package com.tiedup.remake.commands; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.tiedup.remake.entities.*; import com.tiedup.remake.entities.skins.EliteKidnapperSkinManager; import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.base.BindVariant; import com.tiedup.remake.items.base.BlindfoldVariant; import com.tiedup.remake.items.base.EarplugsVariant; import com.tiedup.remake.items.base.GagVariant; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.v2.BodyRegionV2; import java.util.List; import java.util.Optional; 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.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.AABB; /** * NPC management commands. * * Commands: * /npc spawn kidnapper [player] - Spawn a kidnapper at location * /npc spawn elite [player] - Spawn an elite kidnapper * /npc spawn archer [player] - Spawn an archer kidnapper * /npc spawn damsel [player] - Spawn a damsel NPC * /npc kill [radius] - Kill all mod NPCs in radius * /npc tie - Tie the nearest NPC * /npc gag - Gag the nearest NPC * /npc blindfold - Blindfold the nearest NPC * /npc collar - Collar the nearest NPC * /npc untie - Untie the nearest NPC (remove all) * /npc state - Show state of nearest NPC * /npc full - Apply full bondage to nearest NPC * * All commands require OP level 2. */ public class NPCCommand { public static void register( CommandDispatcher dispatcher ) { dispatcher.register(createNPCCommand()); } /** * Create the NPC command builder (for use as subcommand of /tiedup). * @return The command builder */ public static com.mojang.brigadier.builder.LiteralArgumentBuilder< CommandSourceStack > createNPCCommand() { return Commands.literal("npc") .requires(CommandHelper.REQUIRES_OP) .then( Commands.literal("spawn") // /npc spawn kidnapper [player] .then( Commands.literal("kidnapper") .executes(ctx -> spawnKidnapper(ctx, null)) .then( Commands.argument( "player", EntityArgument.player() ).executes(ctx -> spawnKidnapper( ctx, EntityArgument.getPlayer(ctx, "player") ) ) ) ) // /npc spawn elite [player] .then( Commands.literal("elite").then( Commands.argument("name", StringArgumentType.word()) .suggests((ctx, builder) -> { for (KidnapperVariant v : EliteKidnapperSkinManager.CORE.getAllVariants()) { builder.suggest(v.id()); } return builder.buildFuture(); }) .executes(ctx -> spawnElite(ctx, null)) .then( Commands.argument( "player", EntityArgument.player() ).executes(ctx -> spawnElite( ctx, EntityArgument.getPlayer( ctx, "player" ) ) ) ) ) ) // /npc spawn archer [player] .then( Commands.literal("archer") .executes(ctx -> spawnArcher(ctx, null)) .then( Commands.argument( "player", EntityArgument.player() ).executes(ctx -> spawnArcher( ctx, EntityArgument.getPlayer(ctx, "player") ) ) ) ) // /npc spawn damsel [player] .then( Commands.literal("damsel") .executes(ctx -> spawnDamsel(ctx, null)) .then( Commands.argument( "player", EntityArgument.player() ).executes(ctx -> spawnDamsel( ctx, EntityArgument.getPlayer(ctx, "player") ) ) ) ) ) // /npc kill - Kill all mod NPCs in radius .then( Commands.literal("kill") .executes(ctx -> killNPCs(ctx, 10)) .then( Commands.argument( "radius", com.mojang.brigadier.arguments.IntegerArgumentType.integer( 1, 100 ) ).executes(ctx -> killNPCs( ctx, com.mojang.brigadier.arguments.IntegerArgumentType.getInteger( ctx, "radius" ) ) ) ) ) // /npc tie - Tie nearest NPC .then(Commands.literal("tie").executes(NPCCommand::tieNPC)) // /npc gag - Gag nearest NPC .then(Commands.literal("gag").executes(NPCCommand::gagNPC)) // /npc blindfold - Blindfold nearest NPC .then( Commands.literal("blindfold").executes(NPCCommand::blindfoldNPC) ) // /npc collar - Collar nearest NPC .then(Commands.literal("collar").executes(NPCCommand::collarNPC)) // /npc untie - Untie nearest NPC .then(Commands.literal("untie").executes(NPCCommand::untieNPC)) // /npc state - Show NPC state .then(Commands.literal("state").executes(NPCCommand::showNPCState)) // /npc full - Full bondage on nearest NPC .then( Commands.literal("full").executes(NPCCommand::fullBondageNPC) ); } private static int spawnKidnapper( CommandContext context, ServerPlayer targetPlayer ) throws CommandSyntaxException { CommandSourceStack source = context.getSource(); ServerLevel level = source.getLevel(); // Get spawn location double x, y, z; if (targetPlayer != null) { x = targetPlayer.getX(); y = targetPlayer.getY(); z = targetPlayer.getZ(); } else if (source.getEntity() instanceof ServerPlayer player) { x = player.getX(); y = player.getY(); z = player.getZ(); } else { source.sendFailure( Component.literal("Must specify a player or be a player") ); return 0; } // Spawn the kidnapper EntityKidnapper kidnapper = ModEntities.KIDNAPPER.get().create(level); if (kidnapper != null) { kidnapper.moveTo(x, y, z, level.random.nextFloat() * 360F, 0.0F); kidnapper.finalizeSpawn( level, level.getCurrentDifficultyAt(kidnapper.blockPosition()), MobSpawnType.COMMAND, null, null ); level.addFreshEntity(kidnapper); source.sendSuccess( () -> Component.literal( "§aSpawned Kidnapper at " + formatPos(x, y, z) ), true ); return 1; } source.sendFailure(Component.literal("Failed to spawn Kidnapper")); return 0; } private static int spawnElite( CommandContext context, ServerPlayer targetPlayer ) throws CommandSyntaxException { CommandSourceStack source = context.getSource(); ServerLevel level = source.getLevel(); String name = StringArgumentType.getString(context, "name"); // Parse variant KidnapperVariant variant = EliteKidnapperSkinManager.CORE.getVariant( name ); if (variant == null) { source.sendFailure( Component.literal( "Unknown elite variant: " + name + ". Available: suki, carol, athena, evelyn" ) ); return 0; } // Get spawn location double x, y, z; if (targetPlayer != null) { x = targetPlayer.getX(); y = targetPlayer.getY(); z = targetPlayer.getZ(); } else if (source.getEntity() instanceof ServerPlayer player) { x = player.getX(); y = player.getY(); z = player.getZ(); } else { source.sendFailure( Component.literal("Must specify a player or be a player") ); return 0; } // Spawn the elite kidnapper EntityKidnapperElite elite = ModEntities.KIDNAPPER_ELITE.get().create( level ); if (elite != null) { elite.moveTo(x, y, z, level.random.nextFloat() * 360F, 0.0F); elite.setKidnapperVariant(variant); elite.finalizeSpawn( level, level.getCurrentDifficultyAt(elite.blockPosition()), MobSpawnType.COMMAND, null, null ); level.addFreshEntity(elite); source.sendSuccess( () -> Component.literal( "§aSpawned Elite Kidnapper '" + variant.defaultName() + "' at " + formatPos(x, y, z) ), true ); return 1; } source.sendFailure( Component.literal("Failed to spawn Elite Kidnapper") ); return 0; } private static int spawnArcher( CommandContext context, ServerPlayer targetPlayer ) throws CommandSyntaxException { CommandSourceStack source = context.getSource(); ServerLevel level = source.getLevel(); // Get spawn location double x, y, z; if (targetPlayer != null) { x = targetPlayer.getX(); y = targetPlayer.getY(); z = targetPlayer.getZ(); } else if (source.getEntity() instanceof ServerPlayer player) { x = player.getX(); y = player.getY(); z = player.getZ(); } else { source.sendFailure( Component.literal("Must specify a player or be a player") ); return 0; } // Spawn the archer EntityKidnapperArcher archer = ModEntities.KIDNAPPER_ARCHER.get().create(level); if (archer != null) { archer.moveTo(x, y, z, level.random.nextFloat() * 360F, 0.0F); archer.finalizeSpawn( level, level.getCurrentDifficultyAt(archer.blockPosition()), MobSpawnType.COMMAND, null, null ); level.addFreshEntity(archer); source.sendSuccess( () -> Component.literal( "§aSpawned Archer Kidnapper at " + formatPos(x, y, z) ), true ); return 1; } source.sendFailure( Component.literal("Failed to spawn Archer Kidnapper") ); return 0; } private static int spawnDamsel( CommandContext context, ServerPlayer targetPlayer ) throws CommandSyntaxException { CommandSourceStack source = context.getSource(); ServerLevel level = source.getLevel(); // Get spawn location double x, y, z; if (targetPlayer != null) { x = targetPlayer.getX(); y = targetPlayer.getY(); z = targetPlayer.getZ(); } else if (source.getEntity() instanceof ServerPlayer player) { x = player.getX(); y = player.getY(); z = player.getZ(); } else { source.sendFailure( Component.literal("Must specify a player or be a player") ); return 0; } // Spawn the damsel EntityDamsel damsel = ModEntities.DAMSEL.get().create(level); if (damsel != null) { damsel.moveTo(x, y, z, level.random.nextFloat() * 360F, 0.0F); damsel.finalizeSpawn( level, level.getCurrentDifficultyAt(damsel.blockPosition()), MobSpawnType.COMMAND, null, null ); level.addFreshEntity(damsel); source.sendSuccess( () -> Component.literal( "§aSpawned Damsel at " + formatPos(x, y, z) ), true ); return 1; } source.sendFailure(Component.literal("Failed to spawn Damsel")); return 0; } private static int killNPCs( CommandContext context, int radius ) throws CommandSyntaxException { CommandSourceStack source = context.getSource(); Optional playerOpt = CommandHelper.getPlayerOrFail( source ); if (playerOpt.isEmpty()) return 0; ServerPlayer player = playerOpt.get(); ServerLevel level = player.serverLevel(); int killed = 0; // Find and kill all mod NPCs in radius var entities = level.getEntitiesOfClass( net.minecraft.world.entity.LivingEntity.class, player.getBoundingBox().inflate(radius), e -> e instanceof com.tiedup.remake.entities.AbstractTiedUpNpc ); for (var entity : entities) { entity.discard(); killed++; } int finalKilled = killed; source.sendSuccess( () -> Component.literal( "§aKilled " + finalKilled + " mod NPCs in radius " + radius ), true ); return killed; } private static String formatPos(double x, double y, double z) { return String.format("(%.1f, %.1f, %.1f)", x, y, z); } // NPC Bondage Commands (from DamselTestCommand) /** * Find the nearest mod NPC (Damsel or Kidnapper) within 10 blocks. */ private static IBondageState findNearestNPC( CommandContext context ) { Entity source = context.getSource().getEntity(); if (source == null) return null; AABB searchBox = source.getBoundingBox().inflate(10.0); // Search for any IBondageState entity (EntityDamsel implements this) List npcs = source .level() .getEntitiesOfClass(EntityDamsel.class, searchBox); if (npcs.isEmpty()) { return null; } // Return closest NPC return npcs .stream() .min((a, b) -> Double.compare(a.distanceToSqr(source), b.distanceToSqr(source)) ) .orElse(null); } private static int tieNPC(CommandContext context) { IBondageState npc = findNearestNPC(context); if (npc == null) { context .getSource() .sendFailure( Component.literal("No mod NPC found within 10 blocks") ); return 0; } if (npc.isTiedUp()) { context .getSource() .sendFailure(Component.literal("NPC is already tied up")); return 0; } npc.equip( BodyRegionV2.ARMS, new ItemStack(ModItems.getBind(BindVariant.ROPES)) ); context .getSource() .sendSuccess( () -> Component.literal("§aTied up " + npc.getKidnappedName()), true ); return 1; } private static int gagNPC(CommandContext context) { IBondageState npc = findNearestNPC(context); if (npc == null) { context .getSource() .sendFailure( Component.literal("No mod NPC found within 10 blocks") ); return 0; } if (npc.isGagged()) { context .getSource() .sendFailure(Component.literal("NPC is already gagged")); return 0; } npc.equip( BodyRegionV2.MOUTH, new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)) ); context .getSource() .sendSuccess( () -> Component.literal("§aGagged " + npc.getKidnappedName()), true ); return 1; } private static int blindfoldNPC( CommandContext context ) { IBondageState npc = findNearestNPC(context); if (npc == null) { context .getSource() .sendFailure( Component.literal("No mod NPC found within 10 blocks") ); return 0; } if (npc.isBlindfolded()) { context .getSource() .sendFailure(Component.literal("NPC is already blindfolded")); return 0; } npc.equip( BodyRegionV2.EYES, new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC)) ); context .getSource() .sendSuccess( () -> Component.literal( "§aBlindfolded " + npc.getKidnappedName() ), true ); return 1; } private static int collarNPC(CommandContext context) { IBondageState npc = findNearestNPC(context); if (npc == null) { context .getSource() .sendFailure( Component.literal("No mod NPC found within 10 blocks") ); return 0; } if (npc.hasCollar()) { context .getSource() .sendFailure(Component.literal("NPC already has a collar")); return 0; } npc.equip( BodyRegionV2.NECK, new ItemStack(ModItems.CLASSIC_COLLAR.get()) ); context .getSource() .sendSuccess( () -> Component.literal("§aCollared " + npc.getKidnappedName()), true ); return 1; } private static int untieNPC(CommandContext context) { IBondageState npc = findNearestNPC(context); if (npc == null) { context .getSource() .sendFailure( Component.literal("No mod NPC found within 10 blocks") ); return 0; } npc.untie(true); // Drop items context .getSource() .sendSuccess( () -> Component.literal("§aUntied " + npc.getKidnappedName()), true ); return 1; } private static int fullBondageNPC( CommandContext context ) { IBondageState npc = findNearestNPC(context); if (npc == null) { context .getSource() .sendFailure( Component.literal("No mod NPC found within 10 blocks") ); return 0; } // Apply full bondage using AbstractTiedUpNpc method if ( npc instanceof com.tiedup.remake.entities.AbstractTiedUpNpc npcEntity ) { npcEntity.applyBondage( new ItemStack(ModItems.getBind(BindVariant.ROPES)), new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)), new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC)), new ItemStack(ModItems.getEarplugs(EarplugsVariant.CLASSIC)), new ItemStack(ModItems.CLASSIC_COLLAR.get()), ItemStack.EMPTY // No clothes ); } context .getSource() .sendSuccess( () -> Component.literal( "§aFully restrained " + npc.getKidnappedName() ), true ); return 1; } private static int showNPCState( CommandContext context ) { IBondageState npc = findNearestNPC(context); if (npc == null) { context .getSource() .sendFailure( Component.literal("No mod NPC found within 10 blocks") ); return 0; } CommandSourceStack source = context.getSource(); source.sendSuccess( () -> Component.literal( "§6=== NPC State: " + npc.getKidnappedName() + " ===" ), false ); if (npc instanceof EntityDamsel damsel) { source.sendSuccess( () -> Component.literal("§eVariant: §f" + damsel.getVariantId()), false ); source.sendSuccess( () -> Component.literal( "§eSlim Arms: " + (damsel.hasSlimArms() ? "§aYes" : "§7No") ), false ); } source.sendSuccess( () -> Component.literal( "§eTied Up: " + (npc.isTiedUp() ? "§aYes" : "§7No") ), false ); source.sendSuccess( () -> Component.literal( "§eGagged: " + (npc.isGagged() ? "§aYes" : "§7No") ), false ); source.sendSuccess( () -> Component.literal( "§eBlindfolded: " + (npc.isBlindfolded() ? "§aYes" : "§7No") ), false ); source.sendSuccess( () -> Component.literal( "§eHas Collar: " + (npc.hasCollar() ? "§aYes" : "§7No") ), false ); source.sendSuccess( () -> Component.literal( "§eHas Earplugs: " + (npc.hasEarplugs() ? "§aYes" : "§7No") ), false ); source.sendSuccess( () -> Component.literal( "§eIs Captive: " + (npc.isCaptive() ? "§aYes" : "§7No") ), false ); return 1; } }