Files
TiedUp-/src/main/java/com/tiedup/remake/commands/NPCCommand.java
NotEvil fa5cfb913c feat(C-01): i18n main commands — 148 translatable keys
Phase 3: Migrate Component.literal() in all remaining command files.
- NPCCommand (34), CellCommand (33), SocialCommand (16), CollarCommand (25),
  KeyCommand (18), BountyCommand (6), KidnapSetCommand (2), CaptivityDebugCommand (7),
  InventorySubCommand (3), TestAnimSubCommand (2), MasterTestSubCommand (7), DebtSubCommand (8)
- Strip all section sign color codes, use .withStyle(ChatFormatting)
- 148 new keys in en_us.json (command.tiedup.*)
- Debug/dynamic strings intentionally kept as literal
2026-04-15 13:54:26 +02:00

755 lines
26 KiB
Java

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.state.IBondageState;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List;
import java.util.Optional;
import net.minecraft.ChatFormatting;
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 <name> [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<CommandSourceStack> 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 <name> [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 <radius> - 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<CommandSourceStack> 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.translatable("command.tiedup.error.must_be_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.translatable(
"command.tiedup.npc.spawned_kidnapper", formatPos(x, y, z)
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
source.sendFailure(Component.translatable("command.tiedup.npc.spawn_failed_kidnapper"));
return 0;
}
private static int spawnElite(
CommandContext<CommandSourceStack> 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.translatable("command.tiedup.npc.unknown_variant", name)
);
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.translatable("command.tiedup.error.must_be_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.translatable(
"command.tiedup.npc.spawned_elite", variant.defaultName(), formatPos(x, y, z)
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
source.sendFailure(
Component.translatable("command.tiedup.npc.spawn_failed_elite")
);
return 0;
}
private static int spawnArcher(
CommandContext<CommandSourceStack> 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.translatable("command.tiedup.error.must_be_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.translatable(
"command.tiedup.npc.spawned_archer", formatPos(x, y, z)
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
source.sendFailure(
Component.translatable("command.tiedup.npc.spawn_failed_archer")
);
return 0;
}
private static int spawnDamsel(
CommandContext<CommandSourceStack> 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.translatable("command.tiedup.error.must_be_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.translatable(
"command.tiedup.npc.spawned_damsel", formatPos(x, y, z)
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
source.sendFailure(Component.translatable("command.tiedup.npc.spawn_failed_damsel"));
return 0;
}
private static int killNPCs(
CommandContext<CommandSourceStack> context,
int radius
) throws CommandSyntaxException {
CommandSourceStack source = context.getSource();
Optional<ServerPlayer> 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.translatable(
"command.tiedup.npc.killed", finalKilled, radius
).withStyle(ChatFormatting.GREEN),
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<CommandSourceStack> 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<EntityDamsel> 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<CommandSourceStack> context) {
IBondageState npc = findNearestNPC(context);
if (npc == null) {
context
.getSource()
.sendFailure(
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
if (npc.isTiedUp()) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.npc.already_tied"));
return 0;
}
npc.equip(
BodyRegionV2.ARMS,
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes"))
);
context
.getSource()
.sendSuccess(
() -> Component.translatable("command.tiedup.npc.tied", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
private static int gagNPC(CommandContext<CommandSourceStack> context) {
IBondageState npc = findNearestNPC(context);
if (npc == null) {
context
.getSource()
.sendFailure(
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
if (npc.isGagged()) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.npc.already_gagged"));
return 0;
}
npc.equip(
BodyRegionV2.MOUTH,
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag"))
);
context
.getSource()
.sendSuccess(
() -> Component.translatable("command.tiedup.npc.gagged", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
private static int blindfoldNPC(
CommandContext<CommandSourceStack> context
) {
IBondageState npc = findNearestNPC(context);
if (npc == null) {
context
.getSource()
.sendFailure(
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
if (npc.isBlindfolded()) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.npc.already_blindfolded"));
return 0;
}
npc.equip(
BodyRegionV2.EYES,
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold"))
);
context
.getSource()
.sendSuccess(
() ->
Component.translatable("command.tiedup.npc.blindfolded", npc.getKidnappedName()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
private static int collarNPC(CommandContext<CommandSourceStack> context) {
IBondageState npc = findNearestNPC(context);
if (npc == null) {
context
.getSource()
.sendFailure(
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
if (npc.hasCollar()) {
context
.getSource()
.sendFailure(Component.translatable("command.tiedup.npc.already_collared"));
return 0;
}
npc.equip(
BodyRegionV2.NECK,
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_collar"))
);
context
.getSource()
.sendSuccess(
() -> Component.translatable("command.tiedup.npc.collared", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
private static int untieNPC(CommandContext<CommandSourceStack> context) {
IBondageState npc = findNearestNPC(context);
if (npc == null) {
context
.getSource()
.sendFailure(
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
npc.untie(true); // Drop items
context
.getSource()
.sendSuccess(
() -> Component.translatable("command.tiedup.npc.untied", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
private static int fullBondageNPC(
CommandContext<CommandSourceStack> context
) {
IBondageState npc = findNearestNPC(context);
if (npc == null) {
context
.getSource()
.sendFailure(
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
// Apply full bondage using AbstractTiedUpNpc method
if (
npc instanceof
com.tiedup.remake.entities.AbstractTiedUpNpc npcEntity
) {
npcEntity.applyBondage(
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "ropes")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "cloth_gag")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_blindfold")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_earplugs")),
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "classic_collar")),
ItemStack.EMPTY // No clothes
);
}
context
.getSource()
.sendSuccess(
() ->
Component.translatable(
"command.tiedup.npc.fully_restrained", npc.getKidnappedName()
).withStyle(ChatFormatting.GREEN),
true
);
return 1;
}
private static int showNPCState(
CommandContext<CommandSourceStack> context
) {
IBondageState npc = findNearestNPC(context);
if (npc == null) {
context
.getSource()
.sendFailure(
Component.translatable("command.tiedup.npc.no_npc_nearby")
);
return 0;
}
CommandSourceStack source = context.getSource();
source.sendSuccess(
() ->
Component.translatable(
"command.tiedup.npc.state_header", npc.getKidnappedName()
).withStyle(ChatFormatting.GOLD),
false
);
if (npc instanceof EntityDamsel damsel) {
source.sendSuccess(
() ->
Component.translatable("command.tiedup.npc.state_variant", damsel.getVariantId()).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.translatable("command.tiedup.npc.state_slim_arms",
Component.translatable(damsel.hasSlimArms() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
}
source.sendSuccess(
() ->
Component.translatable("command.tiedup.npc.state_tied",
Component.translatable(npc.isTiedUp() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.translatable("command.tiedup.npc.state_gagged",
Component.translatable(npc.isGagged() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.translatable("command.tiedup.npc.state_blindfolded",
Component.translatable(npc.isBlindfolded() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.translatable("command.tiedup.npc.state_collar",
Component.translatable(npc.hasCollar() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.translatable("command.tiedup.npc.state_earplugs",
Component.translatable(npc.hasEarplugs() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
source.sendSuccess(
() ->
Component.translatable("command.tiedup.npc.state_captive",
Component.translatable(npc.isCaptive() ? "command.tiedup.yes" : "command.tiedup.no")
).withStyle(ChatFormatting.YELLOW),
false
);
return 1;
}
}