Compare commits
8 Commits
feature/d0
...
chore/quic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3bdb026f3 | ||
|
|
c1e1f56058 | ||
|
|
f945e9449b | ||
| cc0ce89de5 | |||
|
|
db407ee68f | ||
|
|
d6bb030ad7 | ||
|
|
199bf00aef | ||
| 4a3ff438c2 |
@@ -3,7 +3,7 @@ package com.tiedup.remake.client.animation.render;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
|
||||
import com.tiedup.remake.state.HumanChairHelper;
|
||||
import com.tiedup.remake.util.HumanChairHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.tiedup.remake.client.state.PetBedClientState;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
|
||||
import com.tiedup.remake.state.HumanChairHelper;
|
||||
import com.tiedup.remake.util.HumanChairHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
|
||||
@@ -17,7 +17,7 @@ import com.tiedup.remake.client.state.PetBedClientState;
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
|
||||
import com.tiedup.remake.state.HumanChairHelper;
|
||||
import com.tiedup.remake.util.HumanChairHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
|
||||
|
||||
@@ -13,8 +13,6 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
@@ -433,52 +431,6 @@ public final class GltfAnimationApplier {
|
||||
ContextAnimationFactory.clearCache();
|
||||
}
|
||||
|
||||
// LEGACY F9 DEBUG TOGGLE
|
||||
|
||||
private static boolean debugEnabled = false;
|
||||
|
||||
/**
|
||||
* Toggle debug mode via F9 key.
|
||||
* When enabled, applies handcuffs V2 animation (rightArm + leftArm) to the local player
|
||||
* using STAND_IDLE context. When disabled, clears all V2 animation.
|
||||
*/
|
||||
public static void toggle() {
|
||||
debugEnabled = !debugEnabled;
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Debug toggle: {}",
|
||||
debugEnabled ? "ON" : "OFF"
|
||||
);
|
||||
|
||||
AbstractClientPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) return;
|
||||
|
||||
if (debugEnabled) {
|
||||
ResourceLocation modelLoc = ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup",
|
||||
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
|
||||
);
|
||||
Set<String> armParts = Set.of("rightArm", "leftArm");
|
||||
RegionBoneMapper.BoneOwnership debugOwnership =
|
||||
new RegionBoneMapper.BoneOwnership(armParts, Set.of());
|
||||
applyV2Animation(
|
||||
player,
|
||||
modelLoc,
|
||||
null,
|
||||
AnimationContext.STAND_IDLE,
|
||||
debugOwnership
|
||||
);
|
||||
} else {
|
||||
clearV2Animation(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether F9 debug mode is currently enabled.
|
||||
*/
|
||||
public static boolean isEnabled() {
|
||||
return debugEnabled;
|
||||
}
|
||||
|
||||
// INTERNAL
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.tiedup.remake.client.gltf;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import com.tiedup.remake.client.animation.context.ContextAnimationFactory;
|
||||
import com.tiedup.remake.client.animation.context.ContextGlbRegistry;
|
||||
import com.tiedup.remake.v2.bondage.client.V2BondageRenderLayer;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
|
||||
@@ -13,8 +11,6 @@ import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.EntityRenderersEvent;
|
||||
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;
|
||||
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
@@ -23,24 +19,16 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Forge event registration for the glTF pipeline.
|
||||
* Registers keybind (F9), render layers, and animation factory.
|
||||
* Registers render layers and animation factory.
|
||||
*/
|
||||
public final class GltfClientSetup {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
|
||||
|
||||
private static final String KEY_CATEGORY = "key.categories.tiedup";
|
||||
static final KeyMapping TOGGLE_KEY = new KeyMapping(
|
||||
"key.tiedup.gltf_toggle",
|
||||
InputConstants.Type.KEYSYM,
|
||||
InputConstants.KEY_F9,
|
||||
KEY_CATEGORY
|
||||
);
|
||||
|
||||
private GltfClientSetup() {}
|
||||
|
||||
/**
|
||||
* MOD bus event subscribers (FMLClientSetupEvent, RegisterKeyMappings, AddLayers).
|
||||
* MOD bus event subscribers (FMLClientSetupEvent, AddLayers).
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = "tiedup",
|
||||
@@ -58,21 +46,11 @@ public final class GltfClientSetup {
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterKeybindings(
|
||||
RegisterKeyMappingsEvent event
|
||||
) {
|
||||
event.register(TOGGLE_KEY);
|
||||
LOGGER.info("[GltfPipeline] Keybind registered: F9");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SubscribeEvent
|
||||
public static void onAddLayers(EntityRenderersEvent.AddLayers event) {
|
||||
// Add GltfRenderLayer (prototype/debug with F9 toggle) to player renderers
|
||||
var defaultRenderer = event.getSkin("default");
|
||||
if (defaultRenderer instanceof PlayerRenderer playerRenderer) {
|
||||
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
|
||||
playerRenderer.addLayer(
|
||||
new V2BondageRenderLayer<>(playerRenderer)
|
||||
);
|
||||
@@ -81,10 +59,9 @@ public final class GltfClientSetup {
|
||||
);
|
||||
}
|
||||
|
||||
// Add both layers to slim player renderer (Alex)
|
||||
// Add V2 layer to slim player renderer (Alex)
|
||||
var slimRenderer = event.getSkin("slim");
|
||||
if (slimRenderer instanceof PlayerRenderer playerRenderer) {
|
||||
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
|
||||
playerRenderer.addLayer(
|
||||
new V2BondageRenderLayer<>(playerRenderer)
|
||||
);
|
||||
@@ -143,23 +120,4 @@ public final class GltfClientSetup {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FORGE bus event subscribers (ClientTickEvent for keybind toggle).
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = "tiedup",
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE,
|
||||
value = Dist.CLIENT
|
||||
)
|
||||
public static class ForgeBusEvents {
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
|
||||
while (TOGGLE_KEY.consumeClick()) {
|
||||
GltfAnimationApplier.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
package com.tiedup.remake.client.gltf;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.model.PlayerModel;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.RenderLayerParent;
|
||||
import net.minecraft.client.renderer.entity.layers.RenderLayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
/**
|
||||
* RenderLayer that renders the glTF mesh (handcuffs) on the player.
|
||||
* Only active when enabled and only renders on the local player.
|
||||
* <p>
|
||||
* Uses the live skinning path: reads live skeleton from HumanoidModel
|
||||
* via {@link GltfLiveBoneReader}, following PlayerAnimator + bendy-lib rotations.
|
||||
* Falls back to GLB-internal skinning via {@link GltfSkinningEngine} if live reading fails.
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class GltfRenderLayer
|
||||
extends RenderLayer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>>
|
||||
{
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
|
||||
|
||||
private static final ResourceLocation CUFFS_MODEL =
|
||||
ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup",
|
||||
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
|
||||
);
|
||||
|
||||
public GltfRenderLayer(
|
||||
RenderLayerParent<
|
||||
AbstractClientPlayer,
|
||||
PlayerModel<AbstractClientPlayer>
|
||||
> renderer
|
||||
) {
|
||||
super(renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Y translate offset to place the glTF mesh in the MC PoseStack.
|
||||
* <p>
|
||||
* After LivingEntityRenderer's scale(-1,-1,1) + translate(0,-1.501,0),
|
||||
* the PoseStack origin is at the model top (1.501 blocks above feet), Y-down.
|
||||
* The glTF mesh (MC-converted) has feet at Y=0 and head at Y≈-1.5.
|
||||
* Translating by 1.501 maps glTF feet to PoseStack feet and head to top.
|
||||
*/
|
||||
private static final float ALIGNMENT_Y = 1.501f;
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource buffer,
|
||||
int packedLight,
|
||||
AbstractClientPlayer entity,
|
||||
float limbSwing,
|
||||
float limbSwingAmount,
|
||||
float partialTick,
|
||||
float ageInTicks,
|
||||
float netHeadYaw,
|
||||
float headPitch
|
||||
) {
|
||||
if (!GltfAnimationApplier.isEnabled()) return;
|
||||
if (entity != Minecraft.getInstance().player) return;
|
||||
|
||||
GltfData data = GltfCache.get(CUFFS_MODEL);
|
||||
if (data == null) return;
|
||||
|
||||
// Live path: read skeleton from HumanoidModel (after PlayerAnimator)
|
||||
PlayerModel<AbstractClientPlayer> parentModel = this.getParentModel();
|
||||
Matrix4f[] joints = GltfLiveBoneReader.computeJointMatricesFromModel(
|
||||
parentModel,
|
||||
data,
|
||||
entity
|
||||
);
|
||||
if (joints == null) {
|
||||
// Fallback to GLB-internal path if live reading fails
|
||||
joints = GltfSkinningEngine.computeJointMatrices(data);
|
||||
}
|
||||
|
||||
poseStack.pushPose();
|
||||
|
||||
// Align glTF mesh with MC model (feet-to-feet alignment)
|
||||
poseStack.translate(0, ALIGNMENT_Y, 0);
|
||||
|
||||
GltfMeshRenderer.renderSkinned(
|
||||
data,
|
||||
joints,
|
||||
poseStack,
|
||||
buffer,
|
||||
packedLight,
|
||||
net.minecraft.client.renderer.entity.LivingEntityRenderer.getOverlayCoords(
|
||||
entity,
|
||||
0.0f
|
||||
)
|
||||
);
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
package com.tiedup.remake.commands.subcommands;
|
||||
|
||||
import com.mojang.brigadier.arguments.FloatArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.tiedup.remake.items.base.AdjustmentHelper;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.commands.CommandHelper;
|
||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public class AccessoryCommands {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
// /tiedup putearplugs <player>
|
||||
root.then(
|
||||
Commands.literal("putearplugs")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(AccessoryCommands::putearplugs)
|
||||
)
|
||||
);
|
||||
// /tiedup takeearplugs <player>
|
||||
root.then(
|
||||
Commands.literal("takeearplugs")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(AccessoryCommands::takeearplugs)
|
||||
)
|
||||
);
|
||||
// /tiedup putclothes <player>
|
||||
root.then(
|
||||
Commands.literal("putclothes")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(AccessoryCommands::putclothes)
|
||||
)
|
||||
);
|
||||
// /tiedup takeclothes <player>
|
||||
root.then(
|
||||
Commands.literal("takeclothes")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(AccessoryCommands::takeclothes)
|
||||
)
|
||||
);
|
||||
// /tiedup fullyrestrain <player>
|
||||
root.then(
|
||||
Commands.literal("fullyrestrain")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(AccessoryCommands::fullyrestrain)
|
||||
)
|
||||
);
|
||||
// /tiedup adjust <player> <type:gag|blindfold|all> <value>
|
||||
root.then(
|
||||
Commands.literal("adjust")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player()).then(
|
||||
Commands.argument("type", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> {
|
||||
builder.suggest("gag");
|
||||
builder.suggest("blindfold");
|
||||
builder.suggest("all");
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.then(
|
||||
Commands.argument(
|
||||
"value",
|
||||
FloatArgumentType.floatArg(-4.0f, 4.0f)
|
||||
).executes(AccessoryCommands::adjust)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static int putearplugs(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (state.hasEarplugs()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" already has earplugs"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
|
||||
state.putEarplugsOn(earplugs);
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been given earplugs"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendToTarget(
|
||||
context.getSource().getEntity(),
|
||||
targetPlayer,
|
||||
SystemMessageManager.MessageCategory.EARPLUGS_ON
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int takeearplugs(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!state.hasEarplugs()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" does not have earplugs"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
state.takeEarplugsOff();
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
"'s earplugs have been removed"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
targetPlayer,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
"Your earplugs have been removed!"
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int putclothes(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (state.hasClothes()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" already has clothes"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ItemStack clothes = new ItemStack(ModItems.CLOTHES.get());
|
||||
state.putClothesOn(clothes);
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been given clothes"
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int takeclothes(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!state.hasClothes()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" is not wearing clothes"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ItemStack removed = state.takeClothesOff();
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
if (!removed.isEmpty()) {
|
||||
targetPlayer.drop(removed, false);
|
||||
}
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Removed clothes from " +
|
||||
targetPlayer.getName().getString()
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int fullyrestrain(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int applied = 0;
|
||||
|
||||
if (!state.isTiedUp()) {
|
||||
ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
|
||||
state.putBindOn(ropes);
|
||||
applied++;
|
||||
}
|
||||
|
||||
if (!state.isGagged()) {
|
||||
ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
|
||||
state.putGagOn(gag);
|
||||
applied++;
|
||||
}
|
||||
|
||||
if (!state.isBlindfolded()) {
|
||||
ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
|
||||
state.putBlindfoldOn(blindfold);
|
||||
applied++;
|
||||
}
|
||||
|
||||
if (!state.hasCollar()) {
|
||||
ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
|
||||
if (
|
||||
context.getSource().getEntity() instanceof ServerPlayer executor
|
||||
) {
|
||||
CollarHelper.addOwner(collar, executor);
|
||||
}
|
||||
state.putCollarOn(collar);
|
||||
applied++;
|
||||
}
|
||||
|
||||
if (!state.hasEarplugs()) {
|
||||
ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
|
||||
state.putEarplugsOn(earplugs);
|
||||
applied++;
|
||||
}
|
||||
|
||||
if (applied == 0) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" is already fully restrained"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
int finalApplied = applied;
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been fully restrained (" +
|
||||
finalApplied +
|
||||
" items applied)"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
targetPlayer,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
"You have been fully restrained!"
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int adjust(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
String type = StringArgumentType.getString(context, "type");
|
||||
float value = FloatArgumentType.getFloat(context, "value");
|
||||
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean adjustedGag = false;
|
||||
boolean adjustedBlindfold = false;
|
||||
|
||||
if (type.equals("gag") || type.equals("all")) {
|
||||
ItemStack gag = state.getEquipment(
|
||||
com.tiedup.remake.v2.BodyRegionV2.MOUTH
|
||||
);
|
||||
if (!gag.isEmpty()) {
|
||||
AdjustmentHelper.setAdjustment(gag, value);
|
||||
adjustedGag = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (type.equals("blindfold") || type.equals("all")) {
|
||||
ItemStack blindfold = state.getEquipment(
|
||||
com.tiedup.remake.v2.BodyRegionV2.EYES
|
||||
);
|
||||
if (!blindfold.isEmpty()) {
|
||||
AdjustmentHelper.setAdjustment(blindfold, value);
|
||||
adjustedBlindfold = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!type.equals("gag") &&
|
||||
!type.equals("blindfold") &&
|
||||
!type.equals("all")
|
||||
) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
"Invalid type. Use: gag, blindfold, or all"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!adjustedGag && !adjustedBlindfold) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" has no " +
|
||||
type +
|
||||
" to adjust"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
String items =
|
||||
adjustedGag && adjustedBlindfold
|
||||
? "gag and blindfold"
|
||||
: adjustedGag
|
||||
? "gag"
|
||||
: "blindfold";
|
||||
String valueStr = String.format("%.2f", value);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7aAdjusted " +
|
||||
items +
|
||||
" for " +
|
||||
targetPlayer.getName().getString() +
|
||||
" to " +
|
||||
valueStr +
|
||||
" pixels"
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.tiedup.remake.commands.subcommands;
|
||||
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.commands.CommandHelper;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public class BindCommands {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
// /tiedup tie <player>
|
||||
root.then(
|
||||
Commands.literal("tie")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BindCommands::tie)
|
||||
)
|
||||
);
|
||||
// /tiedup untie <player>
|
||||
root.then(
|
||||
Commands.literal("untie")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BindCommands::untie)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static int tie(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (state.isTiedUp()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" is already tied up"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
|
||||
state.putBindOn(ropes);
|
||||
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been tied up"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendTiedUp(
|
||||
context.getSource().getEntity(),
|
||||
targetPlayer
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int untie(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (
|
||||
!state.isTiedUp() &&
|
||||
!state.isGagged() &&
|
||||
!state.isBlindfolded() &&
|
||||
!state.hasCollar()
|
||||
) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" is not restrained"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean removed = false;
|
||||
if (state.isTiedUp()) {
|
||||
state.takeBindOff();
|
||||
removed = true;
|
||||
}
|
||||
if (state.isGagged()) {
|
||||
state.takeGagOff();
|
||||
removed = true;
|
||||
}
|
||||
if (state.isBlindfolded()) {
|
||||
state.takeBlindfoldOff();
|
||||
removed = true;
|
||||
}
|
||||
if (state.hasCollar()) {
|
||||
state.takeCollarOff();
|
||||
removed = true;
|
||||
}
|
||||
if (state.hasEarplugs()) {
|
||||
state.takeEarplugsOff();
|
||||
removed = true;
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been freed from all restraints"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendFreed(targetPlayer);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.tiedup.remake.commands.subcommands;
|
||||
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.commands.CommandHelper;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public class BlindfoldCommands {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
// /tiedup blindfold <player>
|
||||
root.then(
|
||||
Commands.literal("blindfold")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BlindfoldCommands::blindfold)
|
||||
)
|
||||
);
|
||||
// /tiedup unblind <player>
|
||||
root.then(
|
||||
Commands.literal("unblind")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BlindfoldCommands::unblind)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static int blindfold(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (state.isBlindfolded()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" is already blindfolded"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
|
||||
state.putBlindfoldOn(blindfold);
|
||||
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been blindfolded"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendToTarget(
|
||||
context.getSource().getEntity(),
|
||||
targetPlayer,
|
||||
SystemMessageManager.MessageCategory.BLINDFOLDED
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int unblind(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!state.isBlindfolded()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" is not blindfolded"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
state.takeBlindfoldOff();
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
"'s blindfold has been removed"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendToTarget(
|
||||
context.getSource().getEntity(),
|
||||
targetPlayer,
|
||||
SystemMessageManager.MessageCategory.UNBLINDFOLDED
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,288 @@
|
||||
package com.tiedup.remake.commands.subcommands;
|
||||
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.sync.PacketSyncBindState;
|
||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.commands.CommandHelper;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public class CollarCommands {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
// /tiedup collar <player>
|
||||
root.then(
|
||||
Commands.literal("collar")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommands::collar)
|
||||
)
|
||||
);
|
||||
// /tiedup takecollar <player>
|
||||
root.then(
|
||||
Commands.literal("takecollar")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommands::takecollar)
|
||||
)
|
||||
);
|
||||
// /tiedup enslave <player>
|
||||
root.then(
|
||||
Commands.literal("enslave")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommands::enslave)
|
||||
)
|
||||
);
|
||||
// /tiedup free <player>
|
||||
root.then(
|
||||
Commands.literal("free")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommands::free)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static int collar(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (state.hasCollar()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" already has a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
|
||||
|
||||
if (context.getSource().getEntity() instanceof ServerPlayer executor) {
|
||||
CollarHelper.addOwner(collar, executor);
|
||||
}
|
||||
|
||||
state.putCollarOn(collar);
|
||||
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been collared"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendToTarget(
|
||||
context.getSource().getEntity(),
|
||||
targetPlayer,
|
||||
SystemMessageManager.MessageCategory.COLLARED
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int takecollar(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!state.hasCollar()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" does not have a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
state.takeCollarOff();
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
"'s collar has been removed"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendToTarget(
|
||||
context.getSource().getEntity(),
|
||||
targetPlayer,
|
||||
SystemMessageManager.MessageCategory.UNCOLLARED
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int enslave(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// First fully restrain
|
||||
if (!state.isTiedUp()) {
|
||||
ItemStack ropes = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"));
|
||||
state.putBindOn(ropes);
|
||||
}
|
||||
if (!state.isGagged()) {
|
||||
ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
|
||||
state.putGagOn(gag);
|
||||
}
|
||||
if (!state.isBlindfolded()) {
|
||||
ItemStack blindfold = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"));
|
||||
state.putBlindfoldOn(blindfold);
|
||||
}
|
||||
if (!state.hasCollar()) {
|
||||
ItemStack collar = com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(new net.minecraft.resources.ResourceLocation("tiedup", "classic_collar"));
|
||||
if (
|
||||
context.getSource().getEntity() instanceof ServerPlayer executor
|
||||
) {
|
||||
CollarHelper.addOwner(collar, executor);
|
||||
}
|
||||
state.putCollarOn(collar);
|
||||
}
|
||||
if (!state.hasEarplugs()) {
|
||||
ItemStack earplugs = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs"));
|
||||
state.putEarplugsOn(earplugs);
|
||||
}
|
||||
|
||||
// Capture target (this makes them a captive)
|
||||
if (context.getSource().getEntity() instanceof ServerPlayer master) {
|
||||
PlayerBindState masterState = PlayerBindState.getInstance(master);
|
||||
if (masterState != null && masterState.getCaptorManager() != null) {
|
||||
masterState.getCaptorManager().addCaptive(state);
|
||||
}
|
||||
}
|
||||
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been enslaved"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendEnslaved(
|
||||
context.getSource().getEntity(),
|
||||
targetPlayer
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int free(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!state.isCaptive()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() + " is not captured"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
state.free(true);
|
||||
|
||||
PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(
|
||||
targetPlayer
|
||||
);
|
||||
if (statePacket != null) {
|
||||
ModNetwork.sendToPlayer(statePacket, targetPlayer);
|
||||
}
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been freed from slavery"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendFreed(targetPlayer);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.tiedup.remake.commands.subcommands;
|
||||
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.commands.CommandHelper;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public class GagCommands {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
// /tiedup gag <player>
|
||||
root.then(
|
||||
Commands.literal("gag")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(GagCommands::gag)
|
||||
)
|
||||
);
|
||||
// /tiedup ungag <player>
|
||||
root.then(
|
||||
Commands.literal("ungag")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(GagCommands::ungag)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static int gag(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (state.isGagged()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" is already gagged"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ItemStack gag = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"));
|
||||
state.putGagOn(gag);
|
||||
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
" has been gagged"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendGagged(
|
||||
context.getSource().getEntity(),
|
||||
targetPlayer
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ungag(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
PlayerBindState state = PlayerBindState.getInstance(targetPlayer);
|
||||
|
||||
if (state == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(Component.literal("Failed to get player state"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!state.isGagged()) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() + " is not gagged"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
state.takeGagOff();
|
||||
CommandHelper.syncPlayerState(targetPlayer, state);
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7a" +
|
||||
targetPlayer.getName().getString() +
|
||||
"'s gag has been removed"
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendToTarget(
|
||||
context.getSource().getEntity(),
|
||||
targetPlayer,
|
||||
SystemMessageManager.MessageCategory.UNGAGGED
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -889,6 +889,10 @@ public class EntityKidnapperMerchant extends EntityKidnapperElite {
|
||||
// Clear trading players to prevent dangling references
|
||||
if (!this.level().isClientSide) {
|
||||
int count = tradingPlayers.size();
|
||||
// Clean up reverse-lookup map BEFORE clearing to prevent memory leak
|
||||
for (UUID playerUuid : tradingPlayers) {
|
||||
playerToMerchant.remove(playerUuid);
|
||||
}
|
||||
this.tradingPlayers.clear();
|
||||
if (count > 0) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
|
||||
@@ -1072,7 +1072,7 @@ public class EntityMaster extends EntityKidnapperElite {
|
||||
if (
|
||||
tag != null &&
|
||||
tag.getBoolean(
|
||||
com.tiedup.remake.state.HumanChairHelper.NBT_KEY
|
||||
com.tiedup.remake.util.HumanChairHelper.NBT_KEY
|
||||
)
|
||||
) {
|
||||
bindState.unequip(BodyRegionV2.ARMS);
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.tiedup.remake.dialogue.DialogueBridge;
|
||||
import com.tiedup.remake.entities.EntityMaster;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.HumanChairHelper;
|
||||
import com.tiedup.remake.util.HumanChairHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -384,26 +384,27 @@ public class GenericKnife extends Item implements IKnife {
|
||||
return;
|
||||
}
|
||||
|
||||
// Accessory IS locked - reduce lock resistance
|
||||
// Accessory IS locked - reduce lock resistance via knife cut progress
|
||||
ILockable lockable = (ILockable) accessory.getItem();
|
||||
int currentRes = lockable.getCurrentLockResistance(accessory);
|
||||
int newRes = Math.max(0, currentRes - speed);
|
||||
lockable.setCurrentLockResistance(accessory, newRes);
|
||||
int cutProgress = com.tiedup.remake.util.ItemNBTHelper.getInt(accessory, "knifeCutProgress");
|
||||
int newProgress = cutProgress + speed;
|
||||
int lockResistance = com.tiedup.remake.core.SettingsAccessor.getPadlockResistance(null);
|
||||
com.tiedup.remake.util.ItemNBTHelper.setInt(accessory, "knifeCutProgress", newProgress);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[GenericKnife] {} cutting {} lock: resistance {} -> {}",
|
||||
"[GenericKnife] {} cutting {} lock: progress {} / {}",
|
||||
player.getName().getString(),
|
||||
target,
|
||||
currentRes,
|
||||
newRes
|
||||
newProgress,
|
||||
lockResistance
|
||||
);
|
||||
|
||||
// Check if lock is destroyed
|
||||
if (newRes <= 0) {
|
||||
if (newProgress >= lockResistance) {
|
||||
// Destroy the lock (remove padlock, clear lock state)
|
||||
lockable.setLockedByKeyUUID(accessory, null); // Unlocks and clears locked state
|
||||
lockable.setLockable(accessory, false); // Remove padlock entirely
|
||||
lockable.clearLockResistance(accessory);
|
||||
com.tiedup.remake.util.ItemNBTHelper.remove(accessory, "knifeCutProgress");
|
||||
lockable.setJammed(accessory, false);
|
||||
|
||||
state.clearKnifeCutTarget();
|
||||
|
||||
@@ -214,7 +214,6 @@ public class ItemLockpick extends Item {
|
||||
if (success) {
|
||||
// SUCCESS: Unlock the item, PRESERVE the padlock
|
||||
lockable.setLockedByKeyUUID(targetStack, null); // Unlock
|
||||
lockable.clearLockResistance(targetStack); // Clear struggle progress
|
||||
// lockable stays true - padlock preserved!
|
||||
|
||||
SystemMessageManager.sendToPlayer(
|
||||
|
||||
@@ -222,20 +222,6 @@ public interface ILockable {
|
||||
*/
|
||||
String NBT_JAMMED = "jammed";
|
||||
|
||||
/**
|
||||
* Get the resistance added by the lock for struggle mechanics.
|
||||
*
|
||||
* <p>When locked, this value is added to the item's base resistance.
|
||||
* Configurable via server config and GameRule.</p>
|
||||
*
|
||||
* @return Lock resistance value (default: 250, configurable)
|
||||
*/
|
||||
default int getLockResistance() {
|
||||
return com.tiedup.remake.core.SettingsAccessor.getPadlockResistance(
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the lock is jammed (lockpick failed critically).
|
||||
*
|
||||
@@ -266,63 +252,6 @@ public interface ILockable {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== LOCK RESISTANCE (for struggle) ==========
|
||||
|
||||
/**
|
||||
* NBT key for current lock resistance during struggle.
|
||||
*/
|
||||
String NBT_LOCK_RESISTANCE = "lockResistance";
|
||||
|
||||
/**
|
||||
* Get the current lock resistance remaining for struggle.
|
||||
* Initialized to getLockResistance() (configurable, default 250) when first locked.
|
||||
*
|
||||
* @param stack The ItemStack to check
|
||||
* @return Current lock resistance (0 if not locked or fully struggled)
|
||||
*/
|
||||
default int getCurrentLockResistance(ItemStack stack) {
|
||||
if (stack.isEmpty()) return 0;
|
||||
|
||||
// If locked but no resistance stored yet, initialize it
|
||||
if (
|
||||
isLocked(stack) &&
|
||||
!ItemNBTHelper.contains(stack, NBT_LOCK_RESISTANCE)
|
||||
) {
|
||||
return getLockResistance(); // Configurable via ModConfig
|
||||
}
|
||||
|
||||
return ItemNBTHelper.getInt(stack, NBT_LOCK_RESISTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current lock resistance remaining for struggle.
|
||||
*
|
||||
* @param stack The ItemStack to modify
|
||||
* @param resistance The new resistance value
|
||||
*/
|
||||
default void setCurrentLockResistance(ItemStack stack, int resistance) {
|
||||
ItemNBTHelper.setInt(stack, NBT_LOCK_RESISTANCE, resistance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize lock resistance when item is locked.
|
||||
* Called when setLockedByKeyUUID is called with a non-null UUID.
|
||||
*
|
||||
* @param stack The ItemStack to initialize
|
||||
*/
|
||||
default void initializeLockResistance(ItemStack stack) {
|
||||
setCurrentLockResistance(stack, getLockResistance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear lock resistance when item is unlocked.
|
||||
*
|
||||
* @param stack The ItemStack to clear
|
||||
*/
|
||||
default void clearLockResistance(ItemStack stack) {
|
||||
ItemNBTHelper.remove(stack, NBT_LOCK_RESISTANCE);
|
||||
}
|
||||
|
||||
// ========== LOCK BREAKING (struggle/force) ==========
|
||||
|
||||
/**
|
||||
@@ -334,7 +263,6 @@ public interface ILockable {
|
||||
* <li>Unlocks the item (lockedByKeyUUID = null)</li>
|
||||
* <li>Removes the lockable flag (no more padlock slot)</li>
|
||||
* <li>Clears any jam state</li>
|
||||
* <li>Clears stored lock resistance</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param stack The ItemStack to break the lock on
|
||||
@@ -344,6 +272,5 @@ public interface ILockable {
|
||||
setLockedByKeyUUID(stack, null);
|
||||
setLockable(stack, false);
|
||||
setJammed(stack, false);
|
||||
clearLockResistance(stack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +394,6 @@ public class GenericClothes extends Item implements ILockable, IV2BondageItem {
|
||||
stack.getOrCreateTag().putBoolean(NBT_LOCKED, state);
|
||||
if (!state) {
|
||||
// When unlocking, clear lock-related data
|
||||
clearLockResistance(stack);
|
||||
setJammed(stack, false);
|
||||
}
|
||||
return stack;
|
||||
@@ -439,7 +438,6 @@ public class GenericClothes extends Item implements ILockable, IV2BondageItem {
|
||||
if (keyUUID != null) {
|
||||
tag.putUUID(NBT_LOCKED_BY_KEY_UUID, keyUUID);
|
||||
setLocked(stack, true);
|
||||
initializeLockResistance(stack);
|
||||
} else {
|
||||
tag.remove(NBT_LOCKED_BY_KEY_UUID);
|
||||
setLocked(stack, false);
|
||||
|
||||
@@ -622,18 +622,14 @@ public class StruggleSessionManager {
|
||||
);
|
||||
if (accessoryStack.isEmpty()) return;
|
||||
|
||||
if (
|
||||
accessoryStack.getItem() instanceof
|
||||
com.tiedup.remake.items.base.ILockable lockable
|
||||
) {
|
||||
// Update the lock resistance to match session state
|
||||
lockable.setCurrentLockResistance(
|
||||
accessoryStack,
|
||||
session.getCurrentResistance()
|
||||
);
|
||||
// Sync V2 equipment state
|
||||
V2EquipmentHelper.sync(player);
|
||||
}
|
||||
// Update accessory struggle resistance in dedicated NBT key
|
||||
com.tiedup.remake.util.ItemNBTHelper.setInt(
|
||||
accessoryStack,
|
||||
"accessoryStruggleResistance",
|
||||
session.getCurrentResistance()
|
||||
);
|
||||
// Sync V2 equipment state
|
||||
V2EquipmentHelper.sync(player);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.tiedup.remake.mixin;
|
||||
|
||||
import com.tiedup.remake.state.HumanChairHelper;
|
||||
import com.tiedup.remake.util.HumanChairHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.tiedup.remake.mixin.client;
|
||||
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
|
||||
import com.tiedup.remake.state.HumanChairHelper;
|
||||
import com.tiedup.remake.util.HumanChairHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import net.minecraft.client.Camera;
|
||||
|
||||
@@ -172,47 +172,17 @@ public class PacketLockpickAttempt {
|
||||
!targetStack.isEmpty() &&
|
||||
targetStack.getItem() instanceof ILockable lockable
|
||||
) {
|
||||
// Get lock resistance BEFORE clearing it
|
||||
int lockResistance = lockable.getCurrentLockResistance(targetStack);
|
||||
|
||||
// Unlock the item
|
||||
lockable.setLockedByKeyUUID(targetStack, null);
|
||||
lockable.clearLockResistance(targetStack);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PacketLockpickAttempt] Player {} successfully picked lock on {} ({}, resistance was {})",
|
||||
"[PacketLockpickAttempt] Player {} successfully picked lock on {} ({})",
|
||||
player.getName().getString(),
|
||||
targetStack.getDisplayName().getString(),
|
||||
targetRegion,
|
||||
lockResistance
|
||||
targetRegion
|
||||
);
|
||||
|
||||
// Deduct lock resistance from bind resistance
|
||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
||||
if (state != null && state.isTiedUp() && lockResistance > 0) {
|
||||
int currentBindResistance = state.getCurrentBindResistance();
|
||||
int newBindResistance = Math.max(
|
||||
0,
|
||||
currentBindResistance - lockResistance
|
||||
);
|
||||
state.setCurrentBindResistance(newBindResistance);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PacketLockpickAttempt] Deducted {} from bind resistance: {} -> {}",
|
||||
lockResistance,
|
||||
currentBindResistance,
|
||||
newBindResistance
|
||||
);
|
||||
|
||||
// Check if player escaped (resistance = 0)
|
||||
if (newBindResistance <= 0) {
|
||||
state.getStruggleBinds().successActionExternal(state);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PacketLockpickAttempt] Player {} escaped via lockpick!",
|
||||
player.getName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
// Lock is now unlocked -- bind resistance is tracked separately via IHasResistance
|
||||
}
|
||||
|
||||
// Damage lockpick
|
||||
|
||||
@@ -432,26 +432,6 @@ public class PacketSlaveItemManage {
|
||||
// Lock with keyUUID
|
||||
lockable.setLockedByKeyUUID(itemStack, keyUUID);
|
||||
|
||||
if (
|
||||
region == BodyRegionV2.ARMS &&
|
||||
itemStack.getItem() instanceof com.tiedup.remake.items.base.IHasResistance resistanceItem
|
||||
) {
|
||||
int currentResistance = resistanceItem.getCurrentResistance(
|
||||
itemStack,
|
||||
target
|
||||
);
|
||||
int lockResistance = lockable.getLockResistance(); // Configurable via ModConfig
|
||||
resistanceItem.setCurrentResistance(
|
||||
itemStack,
|
||||
currentResistance + lockResistance
|
||||
);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PacketSlaveItemManage] Added {} lock resistance to bind (total: {})",
|
||||
lockResistance,
|
||||
currentResistance + lockResistance
|
||||
);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PacketSlaveItemManage] {} locked {}'s {} with key {}",
|
||||
sender.getName().getString(),
|
||||
@@ -528,7 +508,6 @@ public class PacketSlaveItemManage {
|
||||
|
||||
// Unlock the item - just unlock, keep padlock attached (can be re-locked)
|
||||
lockable.setLockedByKeyUUID(itemStack, null);
|
||||
lockable.clearLockResistance(itemStack); // Clear any struggle progress
|
||||
// Note: Padlock is only dropped on REMOVE, not on UNLOCK
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.tiedup.remake.state.struggle;
|
||||
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.items.base.ILockable;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.util.ItemNBTHelper;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@@ -32,28 +35,36 @@ public class StruggleAccessory extends StruggleState {
|
||||
this.accessoryRegion = accessoryRegion;
|
||||
}
|
||||
|
||||
/** NBT key for accessory struggle progress (replaces removed ILockable lock resistance). */
|
||||
private static final String NBT_ACCESSORY_STRUGGLE = "accessoryStruggleResistance";
|
||||
|
||||
/**
|
||||
* Get the current resistance for this accessory.
|
||||
* Accessories have 0 base resistance - only lock resistance exists.
|
||||
* Resistance is stored in the item's NBT to persist between attempts.
|
||||
* Accessories have 0 base resistance -- when locked, resistance comes from
|
||||
* {@link SettingsAccessor#getPadlockResistance}. Stored in a dedicated NBT
|
||||
* key on the accessory stack to persist between attempts.
|
||||
*
|
||||
* @param state The player's bind state
|
||||
* @return Current resistance value (0 if not locked, 250 if locked and not yet struggled)
|
||||
* @return Current resistance value (0 if not locked)
|
||||
*/
|
||||
@Override
|
||||
protected int getResistanceState(PlayerBindState state) {
|
||||
ItemStack stack = getAccessoryStack(state);
|
||||
if (stack.isEmpty()) return 0;
|
||||
|
||||
if (stack.getItem() instanceof ILockable lockable) {
|
||||
return lockable.getCurrentLockResistance(stack);
|
||||
if (stack.getItem() instanceof ILockable lockable && lockable.isLocked(stack)) {
|
||||
// If no struggle progress stored yet, initialize from config
|
||||
if (!ItemNBTHelper.contains(stack, NBT_ACCESSORY_STRUGGLE)) {
|
||||
return SettingsAccessor.getPadlockResistance(null);
|
||||
}
|
||||
return ItemNBTHelper.getInt(stack, NBT_ACCESSORY_STRUGGLE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current resistance for this accessory struggle.
|
||||
* Stored in the item's NBT to persist between attempts.
|
||||
* Stored in a dedicated NBT key on the accessory stack.
|
||||
*
|
||||
* @param state The player's bind state
|
||||
* @param resistance The new resistance value
|
||||
@@ -63,9 +74,7 @@ public class StruggleAccessory extends StruggleState {
|
||||
ItemStack stack = getAccessoryStack(state);
|
||||
if (stack.isEmpty()) return;
|
||||
|
||||
if (stack.getItem() instanceof ILockable lockable) {
|
||||
lockable.setCurrentLockResistance(stack, resistance);
|
||||
}
|
||||
ItemNBTHelper.setInt(stack, NBT_ACCESSORY_STRUGGLE, resistance);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,29 +96,15 @@ public class StruggleAccessory extends StruggleState {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only locked accessories can be struggled against
|
||||
if (!(stack.getItem() instanceof ILockable lockable)) {
|
||||
// Only require struggle if LOCKED or has BUILT_IN_LOCK.
|
||||
// Unlocked accessories without built-in lock can be freely removed (no struggle needed).
|
||||
boolean isLocked = stack.getItem() instanceof ILockable lockable && lockable.isLocked(stack);
|
||||
boolean hasBuiltInLock = DataDrivenBondageItem.hasBuiltInLock(stack);
|
||||
if (!isLocked && !hasBuiltInLock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lockable.isLocked(stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the accessory is locked.
|
||||
*
|
||||
* @param state The player's bind state
|
||||
* @return true if the accessory is locked
|
||||
*/
|
||||
@Override
|
||||
protected boolean isItemLocked(PlayerBindState state) {
|
||||
ItemStack stack = getAccessoryStack(state);
|
||||
if (
|
||||
stack.isEmpty() || !(stack.getItem() instanceof ILockable lockable)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return lockable.isLocked(stack);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.tiedup.remake.state.struggle;
|
||||
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
@@ -93,31 +92,6 @@ public class StruggleBinds extends StruggleState {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the bind item is locked.
|
||||
* Used by StruggleState to apply x10 resistance penalty.
|
||||
*
|
||||
* @param state The player's bind state
|
||||
* @return true if the bind is locked
|
||||
*/
|
||||
@Override
|
||||
protected boolean isItemLocked(PlayerBindState state) {
|
||||
Player player = state.getPlayer();
|
||||
if (player == null) return false;
|
||||
|
||||
ItemStack bindStack = V2EquipmentHelper.getInRegion(
|
||||
player,
|
||||
BodyRegionV2.ARMS
|
||||
);
|
||||
if (bindStack.isEmpty()) return false;
|
||||
|
||||
// Works for both V1 (ItemBind) and V2 (DataDrivenBondageItem) via ILockable
|
||||
if (bindStack.getItem() instanceof ILockable lockable) {
|
||||
return lockable.isLocked(bindStack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the player successfully escapes from their binds.
|
||||
* Drops all bondage items and completely unties the player.
|
||||
|
||||
@@ -107,13 +107,12 @@ public class StruggleCollar extends StruggleState {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if locked (works for V1 and V2 via ILockable)
|
||||
if (collar.getItem() instanceof ILockable lockable) {
|
||||
if (!lockable.isLocked(collar)) {
|
||||
TiedUpMod.LOGGER.debug("[StruggleCollar] Collar is not locked");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Only require struggle if LOCKED or has BUILT_IN_LOCK.
|
||||
// Unlocked collars without built-in lock can be freely removed (no struggle needed).
|
||||
boolean isLocked = collar.getItem() instanceof ILockable lockable && lockable.isLocked(collar);
|
||||
boolean hasBuiltInLock = DataDrivenBondageItem.hasBuiltInLock(collar);
|
||||
if (!isLocked && !hasBuiltInLock) {
|
||||
TiedUpMod.LOGGER.debug("[StruggleCollar] Collar is not locked and has no built-in lock — no struggle needed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -212,16 +212,6 @@ public abstract class StruggleState {
|
||||
*/
|
||||
protected abstract boolean canStruggle(PlayerBindState state);
|
||||
|
||||
/**
|
||||
* Check if the item being struggled against is locked.
|
||||
*
|
||||
* @param state The player's bind state
|
||||
* @return true if the item is locked
|
||||
*/
|
||||
protected boolean isItemLocked(PlayerBindState state) {
|
||||
return false; // Default: not locked, subclasses override
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if debug logging is enabled.
|
||||
*
|
||||
|
||||
@@ -70,6 +70,13 @@ public class V2TyingPlayerTask extends TyingPlayerTask {
|
||||
targetEntity.getName().getString()
|
||||
);
|
||||
|
||||
// Validate heldStack hasn't changed during tying (e.g. player swapped items)
|
||||
if (heldStack.isEmpty() || !ItemStack.isSameItemSameTags(heldStack, bind)) {
|
||||
TiedUpMod.LOGGER.warn("[V2TyingPlayerTask] heldStack changed during tying — aborting");
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Equip via V2 system
|
||||
V2EquipResult result = V2EquipmentHelper.equipItem(targetEntity, bind);
|
||||
if (result.isSuccess()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.tiedup.remake.state;
|
||||
package com.tiedup.remake.util;
|
||||
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
@@ -46,9 +46,7 @@ import org.joml.Matrix4f;
|
||||
* pushPose/popPose pair. Joint matrices are computed per-GLB-model
|
||||
* because different GLB models have different skeletons.
|
||||
*
|
||||
* <p>Unlike {@link com.tiedup.remake.client.gltf.GltfRenderLayer},
|
||||
* this layer is always active (no F9 toggle guard) and renders on
|
||||
* ALL entities (no local-player-only guard).
|
||||
* <p>This layer is always active and renders on ALL entities.
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class V2BondageRenderLayer<
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.tiedup.remake.v2.bondage.component;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Component: permanent lock for organic items (slime, vine, web, tape).
|
||||
*
|
||||
* <p>These items cannot have a padlock attached but always require struggle
|
||||
* to remove — they behave as if permanently locked.</p>
|
||||
*
|
||||
* <p>JSON config: {@code "built_in_lock": {}}</p>
|
||||
*/
|
||||
public class BuiltInLockComponent implements IItemComponent {
|
||||
|
||||
private BuiltInLockComponent() {}
|
||||
|
||||
public static IItemComponent fromJson(JsonObject config) {
|
||||
return new BuiltInLockComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean blocksUnequip(ItemStack stack, LivingEntity entity) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@ public enum ComponentType {
|
||||
GPS("gps", GpsComponent::fromJson),
|
||||
CHOKING("choking", ChokingComponent::fromJson),
|
||||
ADJUSTABLE("adjustable", AdjustableComponent::fromJson),
|
||||
OWNERSHIP("ownership", OwnershipComponent::fromJson);
|
||||
OWNERSHIP("ownership", OwnershipComponent::fromJson),
|
||||
BUILT_IN_LOCK("built_in_lock", BuiltInLockComponent::fromJson);
|
||||
|
||||
private final String jsonKey;
|
||||
private final Function<JsonObject, IItemComponent> factory;
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package com.tiedup.remake.v2.bondage.datadriven;
|
||||
|
||||
import com.tiedup.remake.core.ModSounds;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.IRestrainable;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||
import com.tiedup.remake.v2.bondage.V2EquipResult;
|
||||
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||
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.capability.V2EquipmentHelper;
|
||||
import com.tiedup.remake.v2.bondage.component.BuiltInLockComponent;
|
||||
import com.tiedup.remake.v2.bondage.component.ComponentHolder;
|
||||
import com.tiedup.remake.v2.bondage.component.ComponentType;
|
||||
import com.tiedup.remake.v2.bondage.component.IItemComponent;
|
||||
@@ -167,11 +173,39 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
|
||||
return TyingInteractionHelper.handleTying(serverPlayer, target, stack, hand);
|
||||
}
|
||||
|
||||
// NECK: blocked — collar equip requires owner setup (add owner to NBT,
|
||||
// register in CollarRegistry, play sound, sync) which is not yet wired
|
||||
// through interactLivingEntity. TODO: implement collar equip flow.
|
||||
if (regions.contains(BodyRegionV2.NECK)) {
|
||||
TiedUpMod.LOGGER.debug("[DataDrivenBondageItem] NECK equip via right-click not yet implemented");
|
||||
// NECK: collar equip flow
|
||||
if (regions.contains(BodyRegionV2.NECK) && player instanceof ServerPlayer serverPlayer) {
|
||||
// Target must be tied up to collar (V1 rule preserved)
|
||||
IRestrainable targetState = KidnappedHelper.getKidnappedState(target);
|
||||
if (targetState == null || !targetState.isTiedUp()) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
// Distance + line-of-sight
|
||||
if (player.distanceTo(target) > 4.0 || !player.hasLineOfSight(target)) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
// Add player as owner in NBT before equip
|
||||
ItemStack collarCopy = stack.copy();
|
||||
CollarHelper.addOwner(collarCopy, player);
|
||||
|
||||
// Equip via V2 system (handles conflict resolution, displaced items)
|
||||
V2EquipResult result = V2EquipmentHelper.equipItem(target, collarCopy);
|
||||
if (result.isSuccess()) {
|
||||
for (ItemStack displaced : result.displaced()) {
|
||||
target.spawnAtLocation(displaced);
|
||||
}
|
||||
stack.shrink(1);
|
||||
// Play collar equip sound
|
||||
target.level().playSound(null, target.getX(), target.getY(), target.getZ(),
|
||||
ModSounds.COLLAR_PUT.get(),
|
||||
net.minecraft.sounds.SoundSource.PLAYERS, 0.5f, 1.0f);
|
||||
V2EquipmentHelper.sync(target);
|
||||
TiedUpMod.LOGGER.info("[DataDrivenBondageItem] {} collared {} (owner added)",
|
||||
player.getName().getString(), target.getName().getString());
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
@@ -355,6 +389,16 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
|
||||
holder.onEquipped(stack, entity);
|
||||
}
|
||||
|
||||
// Initialize currentResistance in NBT from ResistanceComponent at equip time.
|
||||
// This avoids the MAX-scan fallback in getBaseResistance(entity) which returns
|
||||
// the highest resistance across ALL equipped data-driven items.
|
||||
ResistanceComponent resistComp = getComponent(stack, ComponentType.RESISTANCE, ResistanceComponent.class);
|
||||
if (resistComp != null) {
|
||||
com.tiedup.remake.items.base.IHasResistance resistanceItem =
|
||||
(com.tiedup.remake.items.base.IHasResistance) stack.getItem();
|
||||
resistanceItem.setCurrentResistance(stack, resistComp.getBaseResistance());
|
||||
}
|
||||
|
||||
// NPC speed reduction (players use MovementStyleManager, not this legacy path)
|
||||
if (!(entity instanceof Player)) {
|
||||
Set<BodyRegionV2> regions = getOccupiedRegions(stack);
|
||||
@@ -436,6 +480,11 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
|
||||
);
|
||||
}
|
||||
|
||||
/** Check if the item has a built-in lock (organic items: slime, vine, web, tape). */
|
||||
public static boolean hasBuiltInLock(ItemStack stack) {
|
||||
return getComponent(stack, ComponentType.BUILT_IN_LOCK, BuiltInLockComponent.class) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack-aware padlock attachment check for data-driven items.
|
||||
* Returns false for organic items (slime, vine, web, tape) that have
|
||||
|
||||
@@ -139,7 +139,9 @@ public final class DataDrivenItemRegistry {
|
||||
* Clear all definitions. Called on world unload or for testing.
|
||||
*/
|
||||
public static void clear() {
|
||||
SNAPSHOT = RegistrySnapshot.EMPTY;
|
||||
synchronized (RELOAD_LOCK) {
|
||||
SNAPSHOT = RegistrySnapshot.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== COMPONENT HOLDERS =====
|
||||
|
||||
@@ -129,7 +129,6 @@ public class PacketV2LockToggle {
|
||||
|
||||
UUID keyUUID = heldKey.getKeyUUID(heldKeyStack);
|
||||
lockable.setLockedByKeyUUID(stack, keyUUID);
|
||||
lockable.initializeLockResistance(stack);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[V2LockToggle] Locked region {} on entity {}",
|
||||
@@ -142,16 +141,18 @@ public class PacketV2LockToggle {
|
||||
|
||||
if (hasMasterKey) {
|
||||
lockable.setLockedByKeyUUID(stack, null);
|
||||
lockable.clearLockResistance(stack);
|
||||
} else if (heldKey != null) {
|
||||
UUID keyUUID = heldKey.getKeyUUID(heldKeyStack);
|
||||
if (!lockable.matchesKey(stack, keyUUID)) return;
|
||||
lockable.setLockedByKeyUUID(stack, null);
|
||||
lockable.clearLockResistance(stack);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear partial knife-cut progress and stale struggle resistance on unlock
|
||||
com.tiedup.remake.util.ItemNBTHelper.remove(stack, "knifeCutProgress");
|
||||
com.tiedup.remake.util.ItemNBTHelper.remove(stack, "accessoryStruggleResistance");
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[V2LockToggle] Unlocked region {} on entity {}",
|
||||
region.name(),
|
||||
|
||||
@@ -84,7 +84,21 @@ public class PacketV2StruggleStart {
|
||||
if (stack.getItem() instanceof ILockable lockable) {
|
||||
isLocked = lockable.isLocked(stack);
|
||||
if (isLocked) {
|
||||
resistance += lockable.getCurrentLockResistance(stack);
|
||||
// Only add padlock bonus on FIRST session (fresh resistance = base).
|
||||
// If resistance was already decremented and persisted from a prior
|
||||
// interrupted session, the padlock bonus is already baked in.
|
||||
int baseResistance = resistance; // getCurrentResistance returns base if no NBT
|
||||
com.tiedup.remake.v2.bondage.component.ResistanceComponent comp =
|
||||
com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.getComponent(
|
||||
stack, com.tiedup.remake.v2.bondage.component.ComponentType.RESISTANCE,
|
||||
com.tiedup.remake.v2.bondage.component.ResistanceComponent.class);
|
||||
if (comp != null) {
|
||||
baseResistance = comp.getBaseResistance();
|
||||
}
|
||||
if (resistance >= baseResistance) {
|
||||
// Fresh or fully restored — add padlock bonus
|
||||
resistance += com.tiedup.remake.core.SettingsAccessor.getPadlockResistance(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"components": {
|
||||
"resistance": {
|
||||
"id": "tape"
|
||||
}
|
||||
},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"components": {
|
||||
"resistance": {
|
||||
"id": "slime"
|
||||
}
|
||||
},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
"gagging": {
|
||||
"material": "stuffed"
|
||||
},
|
||||
"adjustable": {}
|
||||
"adjustable": {},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
"gagging": {
|
||||
"material": "tape"
|
||||
},
|
||||
"adjustable": {}
|
||||
"adjustable": {},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
"gagging": {
|
||||
"material": "stuffed"
|
||||
},
|
||||
"adjustable": {}
|
||||
"adjustable": {},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"components": {
|
||||
"resistance": {
|
||||
"id": "vine"
|
||||
}
|
||||
},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"components": {
|
||||
"resistance": {
|
||||
"id": "web"
|
||||
}
|
||||
},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
"gagging": {
|
||||
"material": "stuffed"
|
||||
},
|
||||
"adjustable": {}
|
||||
"adjustable": {},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"components": {
|
||||
"resistance": {
|
||||
"id": "tape"
|
||||
}
|
||||
},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"components": {
|
||||
"resistance": {
|
||||
"id": "slime"
|
||||
}
|
||||
},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
"gagging": {
|
||||
"material": "stuffed"
|
||||
},
|
||||
"adjustable": {}
|
||||
"adjustable": {},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
"gagging": {
|
||||
"material": "tape"
|
||||
},
|
||||
"adjustable": {}
|
||||
"adjustable": {},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
"gagging": {
|
||||
"material": "stuffed"
|
||||
},
|
||||
"adjustable": {}
|
||||
"adjustable": {},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"components": {
|
||||
"resistance": {
|
||||
"id": "vine"
|
||||
}
|
||||
},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"components": {
|
||||
"resistance": {
|
||||
"id": "web"
|
||||
}
|
||||
},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
"gagging": {
|
||||
"material": "stuffed"
|
||||
},
|
||||
"adjustable": {}
|
||||
"adjustable": {},
|
||||
"built_in_lock": {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user