package com.tiedup.remake.commands.subcommands; import com.mojang.brigadier.arguments.FloatArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; 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.items.ModItems; import com.tiedup.remake.items.base.AdjustmentHelper; import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.v2.bondage.CollarHelper; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import net.minecraft.resources.ResourceLocation; import com.tiedup.remake.network.sync.PacketSyncBindState; 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; /** * Bondage-related sub-commands for /tiedup. * Handles: tie, untie, gag, ungag, blindfold, unblind, collar, takecollar, * takeearplugs, putearplugs, takeclothes, putclothes, fullyrestrain, enslave, free, adjust */ @SuppressWarnings("null") public class BondageSubCommand { public static void register( LiteralArgumentBuilder root ) { // /tiedup tie root.then( Commands.literal("tie") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::tie) ) ); // /tiedup untie root.then( Commands.literal("untie") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::untie) ) ); // /tiedup gag root.then( Commands.literal("gag") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::gag) ) ); // /tiedup ungag root.then( Commands.literal("ungag") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::ungag) ) ); // /tiedup blindfold root.then( Commands.literal("blindfold") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::blindfold) ) ); // /tiedup unblind root.then( Commands.literal("unblind") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::unblind) ) ); // /tiedup collar root.then( Commands.literal("collar") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::collar) ) ); // /tiedup takecollar root.then( Commands.literal("takecollar") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::takecollar) ) ); // /tiedup takeearplugs root.then( Commands.literal("takeearplugs") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::takeearplugs) ) ); // /tiedup putearplugs root.then( Commands.literal("putearplugs") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::putearplugs) ) ); // /tiedup takeclothes root.then( Commands.literal("takeclothes") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::takeclothes) ) ); // /tiedup putclothes root.then( Commands.literal("putclothes") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::putclothes) ) ); // /tiedup fullyrestrain root.then( Commands.literal("fullyrestrain") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::fullyrestrain) ) ); // /tiedup enslave root.then( Commands.literal("enslave") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::enslave) ) ); // /tiedup free root.then( Commands.literal("free") .requires(CommandHelper.REQUIRES_OP) .then( Commands.argument( "player", EntityArgument.player() ).executes(BondageSubCommand::free) ) ); // /tiedup adjust 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(BondageSubCommand::adjust) ) ) ) ); } // Command Implementations /** * /tiedup tie * * Forcefully tie a player with ropes (default bind). * Uses ItemRopes from ModItems. */ private static int tie(CommandContext 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; } /** * /tiedup untie * * Forcefully untie a player, removing ALL bondage equipment. */ private static int untie(CommandContext 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; } /** * /tiedup gag * * Forcefully gag a player with cloth gag (default). */ private static int gag(CommandContext 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; } /** * /tiedup blindfold * * Forcefully blindfold a player with classic blindfold (default). */ private static int blindfold(CommandContext 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; } /** * /tiedup collar * * Give a collar to a player (forces collar even if not tied). * The command executor becomes the owner of the collar. */ private static int collar(CommandContext 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 = new ItemStack(ModItems.CLASSIC_COLLAR.get()); 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; } /** * /tiedup free * * Free a player from slavery (removes master relationship). * Does NOT remove collar or other equipment. */ private static int free(CommandContext 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; } /** * /tiedup adjust * * Adjust the Y position of gags and/or blindfolds on a player. * Value range: -4.0 to +4.0 (pixels, 1 pixel = 1/16 block) * * Types: * - gag: Adjust only gag * - blindfold: Adjust only blindfold * - all: Adjust both gag and blindfold */ private static int adjust(CommandContext 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; } /** * /tiedup ungag * * Remove gag from a player. */ private static int ungag(CommandContext 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; } /** * /tiedup unblind * * Remove blindfold from a player. */ private static int unblind(CommandContext 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; } /** * /tiedup takecollar * * Remove collar from a player. */ private static int takecollar(CommandContext 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; } /** * /tiedup takeearplugs * * Remove earplugs from a player. */ private static int takeearplugs(CommandContext 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; } /** * /tiedup takeclothes * * Remove clothes from a player. */ private static int takeclothes(CommandContext 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; } /** * /tiedup putclothes * * Put clothes on a player. */ private static int putclothes(CommandContext 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; } /** * /tiedup putearplugs * * Put earplugs on a player. */ private static int putearplugs(CommandContext 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; } /** * /tiedup fullyrestrain * * Apply full bondage: bind + gag + blindfold + collar + earplugs */ private static int fullyrestrain(CommandContext 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 = new ItemStack(ModItems.CLASSIC_COLLAR.get()); 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; } /** * /tiedup enslave * * Fully restrain and enslave a player. * The command executor becomes the master. */ private static int enslave(CommandContext 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 = new ItemStack(ModItems.CLASSIC_COLLAR.get()); 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; } }