4 Commits

Author SHA1 Message Date
NotEvil
d3bdb026f3 fix: restore missing /tiedup tie command registration (review) 2026-04-15 11:14:28 +02:00
NotEvil
c1e1f56058 refactor: split BondageSubCommand 1207L → 5 focused files (UC-01)
- BindCommands.java: tie, untie (156L)
- GagCommands.java: gag, ungag (140L)
- BlindfoldCommands.java: blindfold, unblind (142L)
- CollarCommands.java: collar, takecollar, enslave, free (288L)
- AccessoryCommands.java: putearplugs, takeearplugs, putclothes,
  takeclothes, fullyrestrain, adjust (469L)
- BondageSubCommand.java: thin delegator (19L)

Zero logic changes — purely mechanical code move.
2026-04-15 11:09:48 +02:00
NotEvil
f945e9449b feat(D-01/E): quickwins — debug toggle, HumanChairHelper move, collar equip
Q1: Remove F9 debug toggle from GltfAnimationApplier + delete dead
    GltfRenderLayer + remove keybind registration

Q2: Move HumanChairHelper from state/ to util/ — pure utility with
    no state dependency. 7 import updates.

Q3: Wire NECK collar equip flow in DataDrivenBondageItem:
    - Target must be tied up (V1 rule preserved)
    - Distance + line-of-sight validation
    - Owner added to NBT before equip via CollarHelper.addOwner()
    - V2EquipmentHelper handles conflict resolution
    - ModSounds.COLLAR_PUT played on success
    - OwnershipComponent.onEquipped registers in CollarRegistry
2026-04-15 10:57:01 +02:00
cc0ce89de5 Merge pull request 'feature/d01-branch-e-resistance' (#10) from feature/d01-branch-e-resistance into develop
Reviewed-on: #10
2026-04-15 01:45:58 +00:00
19 changed files with 1263 additions and 1410 deletions

View File

@@ -3,7 +3,7 @@ package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper; 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.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;

View File

@@ -4,7 +4,7 @@ import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper; 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.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.player.AbstractClientPlayer;

View File

@@ -17,7 +17,7 @@ import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper; import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper; 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.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.IV2BondageEquipment;

View File

@@ -13,8 +13,6 @@ import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
@@ -433,52 +431,6 @@ public final class GltfAnimationApplier {
ContextAnimationFactory.clearCache(); 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 // INTERNAL
/** /**

View File

@@ -1,11 +1,9 @@
package com.tiedup.remake.client.gltf; 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.ContextAnimationFactory;
import com.tiedup.remake.client.animation.context.ContextGlbRegistry; import com.tiedup.remake.client.animation.context.ContextGlbRegistry;
import com.tiedup.remake.v2.bondage.client.V2BondageRenderLayer; import com.tiedup.remake.v2.bondage.client.V2BondageRenderLayer;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.renderer.entity.player.PlayerRenderer; import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener; 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.api.distmarker.Dist;
import net.minecraftforge.client.event.EntityRenderersEvent; import net.minecraftforge.client.event.EntityRenderersEvent;
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent; 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.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
@@ -23,24 +19,16 @@ import org.apache.logging.log4j.Logger;
/** /**
* Forge event registration for the glTF pipeline. * 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 { public final class GltfClientSetup {
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline"); 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() {} private GltfClientSetup() {}
/** /**
* MOD bus event subscribers (FMLClientSetupEvent, RegisterKeyMappings, AddLayers). * MOD bus event subscribers (FMLClientSetupEvent, AddLayers).
*/ */
@Mod.EventBusSubscriber( @Mod.EventBusSubscriber(
modid = "tiedup", 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") @SuppressWarnings("unchecked")
@SubscribeEvent @SubscribeEvent
public static void onAddLayers(EntityRenderersEvent.AddLayers event) { public static void onAddLayers(EntityRenderersEvent.AddLayers event) {
// Add GltfRenderLayer (prototype/debug with F9 toggle) to player renderers
var defaultRenderer = event.getSkin("default"); var defaultRenderer = event.getSkin("default");
if (defaultRenderer instanceof PlayerRenderer playerRenderer) { if (defaultRenderer instanceof PlayerRenderer playerRenderer) {
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
playerRenderer.addLayer( playerRenderer.addLayer(
new V2BondageRenderLayer<>(playerRenderer) 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"); var slimRenderer = event.getSkin("slim");
if (slimRenderer instanceof PlayerRenderer playerRenderer) { if (slimRenderer instanceof PlayerRenderer playerRenderer) {
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
playerRenderer.addLayer( playerRenderer.addLayer(
new V2BondageRenderLayer<>(playerRenderer) 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();
}
}
}
} }

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -1072,7 +1072,7 @@ public class EntityMaster extends EntityKidnapperElite {
if ( if (
tag != null && tag != null &&
tag.getBoolean( tag.getBoolean(
com.tiedup.remake.state.HumanChairHelper.NBT_KEY com.tiedup.remake.util.HumanChairHelper.NBT_KEY
) )
) { ) {
bindState.unequip(BodyRegionV2.ARMS); bindState.unequip(BodyRegionV2.ARMS);

View File

@@ -5,7 +5,7 @@ import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation; 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.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import java.util.EnumSet; import java.util.EnumSet;

View File

@@ -1,6 +1,6 @@
package com.tiedup.remake.mixin; 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.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;

View File

@@ -2,7 +2,7 @@ package com.tiedup.remake.mixin.client;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper; 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.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.client.Camera; import net.minecraft.client.Camera;

View File

@@ -1,4 +1,4 @@
package com.tiedup.remake.state; package com.tiedup.remake.util;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;

View File

@@ -46,9 +46,7 @@ import org.joml.Matrix4f;
* pushPose/popPose pair. Joint matrices are computed per-GLB-model * pushPose/popPose pair. Joint matrices are computed per-GLB-model
* because different GLB models have different skeletons. * because different GLB models have different skeletons.
* *
* <p>Unlike {@link com.tiedup.remake.client.gltf.GltfRenderLayer}, * <p>This layer is always active and renders on ALL entities.
* this layer is always active (no F9 toggle guard) and renders on
* ALL entities (no local-player-only guard).
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class V2BondageRenderLayer< public class V2BondageRenderLayer<

View File

@@ -1,7 +1,12 @@
package com.tiedup.remake.v2.bondage.datadriven; package com.tiedup.remake.v2.bondage.datadriven;
import com.tiedup.remake.core.ModSounds;
import com.tiedup.remake.core.TiedUpMod; 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.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.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.TyingInteractionHelper;
@@ -168,11 +173,39 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
return TyingInteractionHelper.handleTying(serverPlayer, target, stack, hand); return TyingInteractionHelper.handleTying(serverPlayer, target, stack, hand);
} }
// NECK: blocked — collar equip requires owner setup (add owner to NBT, // NECK: collar equip flow
// register in CollarRegistry, play sound, sync) which is not yet wired if (regions.contains(BodyRegionV2.NECK) && player instanceof ServerPlayer serverPlayer) {
// through interactLivingEntity. TODO: implement collar equip flow. // Target must be tied up to collar (V1 rule preserved)
if (regions.contains(BodyRegionV2.NECK)) { IRestrainable targetState = KidnappedHelper.getKidnappedState(target);
TiedUpMod.LOGGER.debug("[DataDrivenBondageItem] NECK equip via right-click not yet implemented"); 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; return InteractionResult.PASS;
} }