Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
This commit is contained in:
176
src/main/java/com/tiedup/remake/commands/BountyCommand.java
Normal file
176
src/main/java/com/tiedup/remake/commands/BountyCommand.java
Normal file
@@ -0,0 +1,176 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.bounty.Bounty;
|
||||
import com.tiedup.remake.bounty.BountyManager;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
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.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Command: /bounty <player>
|
||||
*
|
||||
* Phase 17: Bounty System
|
||||
*
|
||||
* Creates a bounty on a target player using the held item as reward.
|
||||
*
|
||||
* Requirements:
|
||||
* - Must hold an item (the reward)
|
||||
* - Cannot put bounty on yourself
|
||||
* - Respects max bounties limit
|
||||
* - Cannot be tied up when creating bounty
|
||||
*/
|
||||
public class BountyCommand {
|
||||
|
||||
public static void register(
|
||||
CommandDispatcher<CommandSourceStack> dispatcher
|
||||
) {
|
||||
dispatcher.register(createBountyCommand());
|
||||
TiedUpMod.LOGGER.info("Registered /bounty command");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the bounty command builder (for use as subcommand of /tiedup).
|
||||
* @return The command builder
|
||||
*/
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createBountyCommand() {
|
||||
return Commands.literal("bounty").then(
|
||||
Commands.argument("target", EntityArgument.player()).executes(
|
||||
BountyCommand::execute
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static int execute(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
// Must be a player
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "target");
|
||||
|
||||
// Cannot bounty yourself
|
||||
if (player.getUUID().equals(target.getUUID())) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
"You cannot put a bounty on yourself!"
|
||||
).withStyle(ChatFormatting.RED)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if player is tied up
|
||||
IBondageState playerState = KidnappedHelper.getKidnappedState(player);
|
||||
if (playerState != null && playerState.isTiedUp()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
"You cannot create bounties while tied up!"
|
||||
).withStyle(ChatFormatting.RED)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get bounty manager
|
||||
BountyManager manager = BountyManager.get(player.serverLevel());
|
||||
|
||||
// Check bounty limit
|
||||
if (!manager.canCreateBounty(player, player.serverLevel())) {
|
||||
int max = SettingsAccessor.getMaxBounties(
|
||||
player.serverLevel().getGameRules()
|
||||
);
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
"Maximum number (" + max + ") of active bounties reached!"
|
||||
).withStyle(ChatFormatting.RED)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Must hold an item as reward
|
||||
ItemStack heldItem = player.getMainHandItem();
|
||||
if (heldItem.isEmpty()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
"You must hold an item as the reward!"
|
||||
).withStyle(ChatFormatting.RED)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get bounty duration
|
||||
int duration = SettingsAccessor.getBountyDuration(
|
||||
player.serverLevel().getGameRules()
|
||||
);
|
||||
|
||||
// SECURITY FIX: Create reward with count=1 to prevent item duplication
|
||||
// Bug: If player held 64 diamonds, bounty would be 64 diamonds but only cost 1
|
||||
ItemStack rewardItem = heldItem.copy();
|
||||
rewardItem.setCount(1);
|
||||
|
||||
// Create the bounty
|
||||
Bounty bounty = new Bounty(
|
||||
player.getUUID(),
|
||||
player.getName().getString(),
|
||||
target.getUUID(),
|
||||
target.getName().getString(),
|
||||
rewardItem,
|
||||
duration
|
||||
);
|
||||
|
||||
// Consume one item from the stack (not the entire stack!)
|
||||
player.getMainHandItem().shrink(1);
|
||||
|
||||
// Add bounty
|
||||
manager.addBounty(bounty);
|
||||
|
||||
// Notify player
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Bounty created on " + target.getName().getString() + "!"
|
||||
).withStyle(ChatFormatting.GREEN),
|
||||
false
|
||||
);
|
||||
|
||||
// Broadcast to all players
|
||||
player.server
|
||||
.getPlayerList()
|
||||
.broadcastSystemMessage(
|
||||
Component.literal(
|
||||
"[Bounty] " +
|
||||
player.getName().getString() +
|
||||
" has put a bounty on " +
|
||||
target.getName().getString() +
|
||||
"!"
|
||||
).withStyle(ChatFormatting.GOLD),
|
||||
false
|
||||
);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BOUNTY] {} created bounty on {} with reward {}",
|
||||
player.getName().getString(),
|
||||
target.getName().getString(),
|
||||
bounty.getRewardDescription()
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.tiedup.remake.cells.CampOwnership;
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.cells.CellRegistryV2;
|
||||
import com.tiedup.remake.prison.PrisonerManager;
|
||||
import com.tiedup.remake.prison.PrisonerRecord;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Debug commands for the unified captivity system.
|
||||
*
|
||||
* Commands:
|
||||
* /tiedup debug prisoner <player> - Show captivity state for a player
|
||||
* /tiedup debug validate - Validate captivity system consistency
|
||||
* /tiedup debug repair - Repair inconsistencies (WARNING: modifies data)
|
||||
* /tiedup debug camp <campIdPrefix> - Show camp info and indexed cells
|
||||
*/
|
||||
public class CaptivityDebugCommand {
|
||||
|
||||
/**
|
||||
* Create the /tiedup debug command tree.
|
||||
*/
|
||||
public static LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createDebugCommand() {
|
||||
return Commands.literal("debug")
|
||||
.requires(CommandHelper.REQUIRES_OP) // Admin only
|
||||
// /tiedup debug prisoner <player>
|
||||
.then(
|
||||
Commands.literal("prisoner").then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(CaptivityDebugCommand::debugPrisoner)
|
||||
)
|
||||
)
|
||||
// /tiedup debug validate
|
||||
.then(
|
||||
Commands.literal("validate").executes(
|
||||
CaptivityDebugCommand::validateSystem
|
||||
)
|
||||
)
|
||||
// /tiedup debug repair
|
||||
.then(
|
||||
Commands.literal("repair").executes(
|
||||
CaptivityDebugCommand::repairSystem
|
||||
)
|
||||
)
|
||||
// /tiedup debug camp <campIdPrefix>
|
||||
.then(
|
||||
Commands.literal("camp").then(
|
||||
Commands.argument(
|
||||
"campIdPrefix",
|
||||
StringArgumentType.string()
|
||||
).executes(CaptivityDebugCommand::debugCamp)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show captivity state for a player.
|
||||
* /tiedup debug prisoner <player>
|
||||
*/
|
||||
private static int debugPrisoner(CommandContext<CommandSourceStack> ctx) {
|
||||
try {
|
||||
ServerPlayer target = EntityArgument.getPlayer(ctx, "player");
|
||||
ServerLevel level = target.serverLevel();
|
||||
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
PrisonerRecord record = manager.getRecord(target.getUUID());
|
||||
|
||||
StringBuilder debugInfo = new StringBuilder();
|
||||
debugInfo
|
||||
.append("Player: ")
|
||||
.append(target.getName().getString())
|
||||
.append("\n");
|
||||
debugInfo.append("State: ").append(record.getState()).append("\n");
|
||||
debugInfo
|
||||
.append("Camp ID: ")
|
||||
.append(record.getCampId())
|
||||
.append("\n");
|
||||
debugInfo
|
||||
.append("Cell ID: ")
|
||||
.append(record.getCellId())
|
||||
.append("\n");
|
||||
debugInfo
|
||||
.append("Captor ID: ")
|
||||
.append(record.getCaptorId())
|
||||
.append("\n");
|
||||
debugInfo
|
||||
.append("Protection Expiry: ")
|
||||
.append(record.getProtectionExpiry())
|
||||
.append("\n");
|
||||
debugInfo
|
||||
.append("Is Protected: ")
|
||||
.append(record.isProtected(level.getGameTime()))
|
||||
.append("\n");
|
||||
debugInfo
|
||||
.append("Is Captive: ")
|
||||
.append(record.isCaptive())
|
||||
.append("\n");
|
||||
|
||||
// Send debug info to command executor
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"=== Captivity Debug Info ===\n" + debugInfo
|
||||
).withStyle(ChatFormatting.YELLOW),
|
||||
false
|
||||
);
|
||||
|
||||
return 1; // Success
|
||||
} catch (Exception e) {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal("Error: " + e.getMessage()).withStyle(
|
||||
ChatFormatting.RED
|
||||
)
|
||||
);
|
||||
return 0; // Failure
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate captivity system consistency.
|
||||
* /tiedup debug validate
|
||||
* NOTE: CaptivitySystemValidator was removed. This now shows a summary of the prison system.
|
||||
*/
|
||||
private static int validateSystem(CommandContext<CommandSourceStack> ctx) {
|
||||
try {
|
||||
ServerLevel level = ctx.getSource().getLevel();
|
||||
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Checking captivity system..."
|
||||
).withStyle(ChatFormatting.YELLOW),
|
||||
true
|
||||
);
|
||||
|
||||
// Show PrisonerManager stats instead
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
String debugInfo = manager.toDebugString();
|
||||
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(debugInfo).withStyle(
|
||||
ChatFormatting.GREEN
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
return 1; // Success
|
||||
} catch (Exception e) {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
"Error during validation: " + e.getMessage()
|
||||
).withStyle(ChatFormatting.RED)
|
||||
);
|
||||
return 0; // Failure
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair captivity system inconsistencies.
|
||||
* /tiedup debug repair
|
||||
* NOTE: CaptivitySystemValidator was removed. This command is now a placeholder.
|
||||
*/
|
||||
private static int repairSystem(CommandContext<CommandSourceStack> ctx) {
|
||||
try {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Repair functionality has been simplified with the new PrisonerManager system."
|
||||
).withStyle(ChatFormatting.YELLOW),
|
||||
true
|
||||
);
|
||||
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"The new system maintains consistency automatically."
|
||||
).withStyle(ChatFormatting.GREEN),
|
||||
true
|
||||
);
|
||||
|
||||
return 1; // Success
|
||||
} catch (Exception e) {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal("Error: " + e.getMessage()).withStyle(
|
||||
ChatFormatting.RED
|
||||
)
|
||||
);
|
||||
return 0; // Failure
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug camp information and indexed cells.
|
||||
* /tiedup debug camp <campIdPrefix>
|
||||
*/
|
||||
private static int debugCamp(CommandContext<CommandSourceStack> ctx) {
|
||||
try {
|
||||
ServerLevel level = ctx.getSource().getLevel();
|
||||
String campIdPrefix = StringArgumentType.getString(
|
||||
ctx,
|
||||
"campIdPrefix"
|
||||
);
|
||||
|
||||
CampOwnership ownership = CampOwnership.get(level);
|
||||
CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
|
||||
|
||||
// Find camps matching prefix
|
||||
UUID matchingCamp = null;
|
||||
for (CampOwnership.CampData camp : ownership.getAllCamps()) {
|
||||
String campIdStr = camp.getCampId().toString();
|
||||
if (campIdStr.startsWith(campIdPrefix)) {
|
||||
matchingCamp = camp.getCampId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingCamp == null) {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
"No camp found with ID prefix: " + campIdPrefix
|
||||
).withStyle(ChatFormatting.RED)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get camp info
|
||||
CampOwnership.CampData campData = ownership.getCamp(matchingCamp);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(
|
||||
String.format(
|
||||
"=== Camp %s ===\n",
|
||||
matchingCamp.toString().substring(0, 8)
|
||||
)
|
||||
);
|
||||
sb.append(String.format("Trader: %s\n", campData.getTraderUUID()));
|
||||
sb.append(String.format("Maid: %s\n", campData.getMaidUUID()));
|
||||
sb.append(String.format("Alive: %s\n", campData.isAlive()));
|
||||
sb.append(String.format("Center: %s\n", campData.getCenter()));
|
||||
|
||||
// Get indexed cells
|
||||
List<CellDataV2> cells = cellRegistry.getCellsByCamp(matchingCamp);
|
||||
sb.append(
|
||||
String.format("\n=== Indexed Cells (%d) ===\n", cells.size())
|
||||
);
|
||||
|
||||
if (cells.isEmpty()) {
|
||||
sb.append("⚠ NO CELLS INDEXED for this camp!\n");
|
||||
} else {
|
||||
for (CellDataV2 cell : cells.subList(
|
||||
0,
|
||||
Math.min(cells.size(), 10)
|
||||
)) {
|
||||
sb.append(
|
||||
String.format(
|
||||
"- Cell %s at %s (type=%s, owner=%s, interior=%d, walls=%d)\n",
|
||||
cell.getId().toString().substring(0, 8),
|
||||
cell.getCorePos().toShortString(),
|
||||
cell.isCampOwned() ? "CAMP" : "PLAYER",
|
||||
cell.getOwnerId() != null
|
||||
? cell.getOwnerId().toString().substring(0, 8)
|
||||
: "null",
|
||||
cell.getInteriorBlocks().size(),
|
||||
cell.getWallBlocks().size()
|
||||
)
|
||||
);
|
||||
}
|
||||
if (cells.size() > 10) {
|
||||
sb.append(
|
||||
String.format("... and %d more\n", cells.size() - 10)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final String campInfo = sb.toString();
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(campInfo).withStyle(
|
||||
ChatFormatting.YELLOW
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
return 1; // Success
|
||||
} catch (Exception e) {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal("Error: " + e.getMessage()).withStyle(
|
||||
ChatFormatting.RED
|
||||
)
|
||||
);
|
||||
return 0; // Failure
|
||||
}
|
||||
}
|
||||
}
|
||||
678
src/main/java/com/tiedup/remake/commands/CellCommand.java
Normal file
678
src/main/java/com/tiedup/remake/commands/CellCommand.java
Normal file
@@ -0,0 +1,678 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.blocks.entity.MarkerBlockEntity;
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.cells.CellOwnerType;
|
||||
import com.tiedup.remake.cells.CellRegistryV2;
|
||||
import com.tiedup.remake.items.ItemAdminWand;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
/**
|
||||
* Cell management commands.
|
||||
*
|
||||
* Phase: Kidnapper Revamp - Cell System
|
||||
*
|
||||
* Commands:
|
||||
* /tiedup cell name <name> - Name the selected cell (from wand) and link to player
|
||||
* /tiedup cell list [owner] - List all cells, optionally filtered by owner
|
||||
* /tiedup cell info [name] - Show info about selected cell or by name
|
||||
* /tiedup cell delete - Delete the selected cell
|
||||
*
|
||||
* Requires OP level 2.
|
||||
*/
|
||||
public class CellCommand {
|
||||
|
||||
/**
|
||||
* Create the cell command builder (for use as subcommand of /tiedup).
|
||||
*/
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createCellCommand() {
|
||||
return Commands.literal("cell")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
// /tiedup cell name <name>
|
||||
.then(
|
||||
Commands.literal("name").then(
|
||||
Commands.argument(
|
||||
"name",
|
||||
StringArgumentType.word()
|
||||
).executes(CellCommand::setName)
|
||||
)
|
||||
)
|
||||
// /tiedup cell list [owner]
|
||||
.then(
|
||||
Commands.literal("list")
|
||||
.executes(CellCommand::listAll)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"owner",
|
||||
EntityArgument.player()
|
||||
).executes(CellCommand::listByOwner)
|
||||
)
|
||||
)
|
||||
// /tiedup cell info [name]
|
||||
.then(
|
||||
Commands.literal("info")
|
||||
.executes(CellCommand::infoSelected)
|
||||
.then(
|
||||
Commands.argument(
|
||||
"name",
|
||||
StringArgumentType.word()
|
||||
).executes(CellCommand::infoByName)
|
||||
)
|
||||
)
|
||||
// /tiedup cell delete
|
||||
.then(
|
||||
Commands.literal("delete").executes(CellCommand::deleteSelected)
|
||||
)
|
||||
// /tiedup cell resetspawns [radius]
|
||||
.then(
|
||||
Commands.literal("resetspawns")
|
||||
.executes(ctx -> resetSpawns(ctx, 100)) // Default 100 block radius
|
||||
.then(
|
||||
Commands.argument(
|
||||
"radius",
|
||||
IntegerArgumentType.integer(1, 500)
|
||||
).executes(ctx ->
|
||||
resetSpawns(
|
||||
ctx,
|
||||
IntegerArgumentType.getInteger(ctx, "radius")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup cell name <name>
|
||||
*
|
||||
* Name the selected cell (from wand) and link it to the executing player.
|
||||
*/
|
||||
private static int setName(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
String name = StringArgumentType.getString(context, "name");
|
||||
|
||||
// Must be a player
|
||||
if (!(source.getEntity() instanceof ServerPlayer player)) {
|
||||
source.sendFailure(Component.literal("Must be a player"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
ServerLevel serverLevel = player.serverLevel();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
|
||||
// Get selected cell from wand
|
||||
UUID selectedCellId = getSelectedCellFromWand(player);
|
||||
if (selectedCellId == null) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
"No cell selected. Use the Admin Wand on a Cell Core first."
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CellDataV2 cell = registry.getCell(selectedCellId);
|
||||
if (cell == null) {
|
||||
source.sendFailure(
|
||||
Component.literal("Selected cell no longer exists")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check name uniqueness
|
||||
CellDataV2 existingCell = registry.getCellByName(name);
|
||||
if (
|
||||
existingCell != null && !existingCell.getId().equals(selectedCellId)
|
||||
) {
|
||||
source.sendFailure(
|
||||
Component.literal("Cell name '" + name + "' already exists")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Set name and owner
|
||||
cell.setName(name);
|
||||
|
||||
// MEDIUM FIX: Update camp index when changing ownership
|
||||
// Store old ownerId before changing (in case it was camp-owned)
|
||||
UUID oldOwnerId = cell.isCampOwned() ? cell.getOwnerId() : null;
|
||||
cell.setOwnerId(player.getUUID());
|
||||
cell.setOwnerType(CellOwnerType.PLAYER);
|
||||
registry.updateCampIndex(cell, oldOwnerId);
|
||||
|
||||
registry.setDirty();
|
||||
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Named cell '" + name + "' and linked to you"
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup cell list
|
||||
*
|
||||
* List all registered cells.
|
||||
*/
|
||||
private static int listAll(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerLevel serverLevel = source.getLevel();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
|
||||
Collection<CellDataV2> cells = registry.getAllCells();
|
||||
if (cells.isEmpty()) {
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("No cells registered"),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("=== Cells (" + cells.size() + ") ==="),
|
||||
false
|
||||
);
|
||||
|
||||
for (CellDataV2 cell : cells) {
|
||||
String info = formatCellInfo(cell, serverLevel);
|
||||
source.sendSuccess(() -> Component.literal(info), false);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup cell list <owner>
|
||||
*
|
||||
* List cells owned by a specific player.
|
||||
*/
|
||||
private static int listByOwner(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerPlayer owner = EntityArgument.getPlayer(context, "owner");
|
||||
ServerLevel serverLevel = source.getLevel();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
|
||||
List<CellDataV2> cells = registry.getCellsByOwner(owner.getUUID());
|
||||
if (cells.isEmpty()) {
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
owner.getName().getString() + " has no cells"
|
||||
),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"=== Cells owned by " +
|
||||
owner.getName().getString() +
|
||||
" (" +
|
||||
cells.size() +
|
||||
") ==="
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
for (CellDataV2 cell : cells) {
|
||||
String info = formatCellInfo(cell, serverLevel);
|
||||
source.sendSuccess(() -> Component.literal(info), false);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup cell info
|
||||
*
|
||||
* Show info about the selected cell (from wand).
|
||||
*/
|
||||
private static int infoSelected(
|
||||
CommandContext<CommandSourceStack> context
|
||||
) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
// Must be a player
|
||||
if (!(source.getEntity() instanceof ServerPlayer player)) {
|
||||
source.sendFailure(Component.literal("Must be a player"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
ServerLevel serverLevel = player.serverLevel();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
|
||||
UUID selectedCellId = getSelectedCellFromWand(player);
|
||||
if (selectedCellId == null) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
"No cell selected. Use the Admin Wand on a Cell Core first."
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CellDataV2 cell = registry.getCell(selectedCellId);
|
||||
if (cell == null) {
|
||||
source.sendFailure(
|
||||
Component.literal("Selected cell no longer exists")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
displayCellInfo(source, cell, serverLevel);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup cell info <name>
|
||||
*
|
||||
* Show info about a cell by name.
|
||||
*/
|
||||
private static int infoByName(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
String name = StringArgumentType.getString(context, "name");
|
||||
ServerLevel serverLevel = source.getLevel();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
|
||||
CellDataV2 cell = registry.getCellByName(name);
|
||||
if (cell == null) {
|
||||
source.sendFailure(
|
||||
Component.literal("Cell '" + name + "' not found")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
displayCellInfo(source, cell, serverLevel);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup cell delete
|
||||
*
|
||||
* Delete the selected cell (from wand).
|
||||
*/
|
||||
private static int deleteSelected(
|
||||
CommandContext<CommandSourceStack> context
|
||||
) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
// Must be a player
|
||||
if (!(source.getEntity() instanceof ServerPlayer player)) {
|
||||
source.sendFailure(Component.literal("Must be a player"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
ServerLevel serverLevel = player.serverLevel();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
|
||||
UUID selectedCellId = getSelectedCellFromWand(player);
|
||||
if (selectedCellId == null) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
"No cell selected. Use the Admin Wand on a Cell Core first."
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CellDataV2 cell = registry.getCell(selectedCellId);
|
||||
if (cell == null) {
|
||||
source.sendFailure(
|
||||
Component.literal("Selected cell no longer exists")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
String cellName =
|
||||
cell.getName() != null
|
||||
? cell.getName()
|
||||
: cell.getId().toString().substring(0, 8) + "...";
|
||||
|
||||
// Remove cell from registry
|
||||
registry.removeCell(selectedCellId);
|
||||
|
||||
// Clear selection from wand
|
||||
ItemStack mainHand = player.getMainHandItem();
|
||||
if (mainHand.getItem() instanceof ItemAdminWand) {
|
||||
ItemAdminWand.setActiveCellId(mainHand, null);
|
||||
}
|
||||
ItemStack offHand = player.getOffhandItem();
|
||||
if (offHand.getItem() instanceof ItemAdminWand) {
|
||||
ItemAdminWand.setActiveCellId(offHand, null);
|
||||
}
|
||||
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("Deleted cell '" + cellName + "'"),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup cell resetspawns [radius]
|
||||
*
|
||||
* Reset the hasSpawned flag on all spawn markers within radius.
|
||||
* Use this before saving a structure to ensure NPCs will spawn when placed.
|
||||
*
|
||||
* Performance: Iterates over loaded chunks and their BlockEntities instead of
|
||||
* individual block positions to avoid server crash on large radii.
|
||||
*/
|
||||
private static int resetSpawns(
|
||||
CommandContext<CommandSourceStack> context,
|
||||
int radius
|
||||
) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerLevel serverLevel = source.getLevel();
|
||||
BlockPos center = BlockPos.containing(source.getPosition());
|
||||
|
||||
int resetCount = 0;
|
||||
int spawnMarkerCount = 0;
|
||||
|
||||
// Convert block radius to chunk radius (16 blocks per chunk)
|
||||
int chunkRadius = (radius + 15) >> 4;
|
||||
int centerChunkX = center.getX() >> 4;
|
||||
int centerChunkZ = center.getZ() >> 4;
|
||||
|
||||
// Iterate over chunks instead of individual blocks (much faster)
|
||||
for (
|
||||
int cx = centerChunkX - chunkRadius;
|
||||
cx <= centerChunkX + chunkRadius;
|
||||
cx++
|
||||
) {
|
||||
for (
|
||||
int cz = centerChunkZ - chunkRadius;
|
||||
cz <= centerChunkZ + chunkRadius;
|
||||
cz++
|
||||
) {
|
||||
// Only process loaded chunks
|
||||
net.minecraft.world.level.chunk.LevelChunk chunk = serverLevel
|
||||
.getChunkSource()
|
||||
.getChunkNow(cx, cz);
|
||||
if (chunk == null) continue;
|
||||
|
||||
// Iterate over all BlockEntities in this chunk
|
||||
for (BlockEntity be : chunk.getBlockEntities().values()) {
|
||||
if (!(be instanceof MarkerBlockEntity marker)) continue;
|
||||
|
||||
// Check if within radius (squared distance for performance)
|
||||
BlockPos pos = be.getBlockPos();
|
||||
double distSq = center.distSqr(pos);
|
||||
if (distSq > (double) radius * radius) continue;
|
||||
|
||||
if (marker.isSpawnMarker()) {
|
||||
spawnMarkerCount++;
|
||||
if (marker.hasSpawned()) {
|
||||
marker.resetHasSpawned();
|
||||
resetCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final int finalResetCount = resetCount;
|
||||
final int finalSpawnMarkerCount = spawnMarkerCount;
|
||||
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Reset " +
|
||||
finalResetCount +
|
||||
" spawn markers (found " +
|
||||
finalSpawnMarkerCount +
|
||||
" total spawn markers in " +
|
||||
radius +
|
||||
" block radius)"
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
if (resetCount > 0) {
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"You can now save the structure - NPCs will spawn when it's placed."
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ==================== HELPERS ====================
|
||||
|
||||
/**
|
||||
* Get the selected cell ID from the player's admin wand.
|
||||
*/
|
||||
private static UUID getSelectedCellFromWand(ServerPlayer player) {
|
||||
// Check main hand
|
||||
ItemStack mainHand = player.getMainHandItem();
|
||||
if (mainHand.getItem() instanceof ItemAdminWand) {
|
||||
UUID cellId = ItemAdminWand.getActiveCellId(mainHand);
|
||||
if (cellId != null) return cellId;
|
||||
}
|
||||
|
||||
// Check offhand
|
||||
ItemStack offHand = player.getOffhandItem();
|
||||
if (offHand.getItem() instanceof ItemAdminWand) {
|
||||
return ItemAdminWand.getActiveCellId(offHand);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format cell info for list display.
|
||||
*/
|
||||
private static String formatCellInfo(CellDataV2 cell, ServerLevel level) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// Name or ID
|
||||
if (cell.getName() != null) {
|
||||
sb.append(" ").append(cell.getName());
|
||||
} else {
|
||||
sb
|
||||
.append(" ")
|
||||
.append(cell.getId().toString().substring(0, 8))
|
||||
.append("...");
|
||||
}
|
||||
|
||||
// Position
|
||||
sb.append(" @ ").append(cell.getCorePos().toShortString());
|
||||
|
||||
// Owner
|
||||
if (cell.hasOwner()) {
|
||||
ServerPlayer owner = level
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(cell.getOwnerId());
|
||||
if (owner != null) {
|
||||
sb.append(" (").append(owner.getName().getString()).append(")");
|
||||
} else {
|
||||
sb.append(" (offline)");
|
||||
}
|
||||
} else {
|
||||
sb.append(" (world)");
|
||||
}
|
||||
|
||||
// Prisoners
|
||||
if (cell.isOccupied()) {
|
||||
sb
|
||||
.append(" [")
|
||||
.append(cell.getPrisonerCount())
|
||||
.append(" prisoners]");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display detailed cell info (V2).
|
||||
*/
|
||||
private static void displayCellInfo(
|
||||
CommandSourceStack source,
|
||||
CellDataV2 cell,
|
||||
ServerLevel level
|
||||
) {
|
||||
String nameDisplay =
|
||||
cell.getName() != null ? cell.getName() : "(unnamed)";
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("=== Cell: " + nameDisplay + " ==="),
|
||||
false
|
||||
);
|
||||
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("ID: " + cell.getId().toString()),
|
||||
false
|
||||
);
|
||||
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("State: " + cell.getState()),
|
||||
false
|
||||
);
|
||||
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Core Position: " + cell.getCorePos().toShortString()
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
// Spawn point (may be null in V2)
|
||||
if (cell.getSpawnPoint() != null) {
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Spawn Point: " + cell.getSpawnPoint().toShortString()
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// Owner info
|
||||
if (cell.hasOwner()) {
|
||||
ServerPlayer owner = level
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(cell.getOwnerId());
|
||||
String ownerName =
|
||||
owner != null ? owner.getName().getString() : "(offline)";
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Owner: " +
|
||||
ownerName +
|
||||
" (" +
|
||||
cell.getOwnerId().toString().substring(0, 8) +
|
||||
"...)"
|
||||
),
|
||||
false
|
||||
);
|
||||
} else {
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("Owner: (world-generated)"),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// Geometry
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Interior blocks: " + cell.getInteriorBlocks().size()
|
||||
),
|
||||
false
|
||||
);
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Wall blocks: " + cell.getWallBlocks().size()
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
// Breach info
|
||||
if (!cell.getBreachedPositions().isEmpty()) {
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Breaches: " +
|
||||
cell.getBreachedPositions().size() +
|
||||
" (" +
|
||||
String.format(
|
||||
"%.1f",
|
||||
cell.getBreachPercentage() * 100
|
||||
) +
|
||||
"%)"
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// Features
|
||||
if (!cell.getBeds().isEmpty()) {
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("Beds: " + cell.getBeds().size()),
|
||||
false
|
||||
);
|
||||
}
|
||||
if (!cell.getAnchors().isEmpty()) {
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("Anchors: " + cell.getAnchors().size()),
|
||||
false
|
||||
);
|
||||
}
|
||||
if (!cell.getDoors().isEmpty()) {
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("Doors: " + cell.getDoors().size()),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// Prisoners
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Prisoners: " + cell.getPrisonerCount() + "/4"
|
||||
),
|
||||
false
|
||||
);
|
||||
for (UUID prisonerId : cell.getPrisonerIds()) {
|
||||
ServerPlayer prisoner = level
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(prisonerId);
|
||||
String prisonerName =
|
||||
prisoner != null ? prisoner.getName().getString() : "(offline)";
|
||||
source.sendSuccess(
|
||||
() -> Component.literal(" - " + prisonerName),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
311
src/main/java/com/tiedup/remake/commands/ClothesCommand.java
Normal file
311
src/main/java/com/tiedup/remake/commands/ClothesCommand.java
Normal file
@@ -0,0 +1,311 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
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.items.clothes.GenericClothes;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Command handler for clothes configuration.
|
||||
*
|
||||
* Subcommands:
|
||||
* /tiedup clothes url set <url> - Set dynamic texture URL on held clothes
|
||||
* /tiedup clothes url reset - Remove dynamic texture URL
|
||||
* /tiedup clothes fullskin - Toggle full-skin mode
|
||||
* /tiedup clothes smallarms - Toggle small arms forcing
|
||||
* /tiedup clothes keephead - Toggle keep head mode (preserves wearer's head)
|
||||
* /tiedup clothes layer <part> - Toggle layer visibility
|
||||
*
|
||||
* All commands operate on the clothes item held in main hand.
|
||||
*/
|
||||
public class ClothesCommand {
|
||||
|
||||
/**
|
||||
* Create the /tiedup clothes ... command tree.
|
||||
*
|
||||
* @return The command builder
|
||||
*/
|
||||
public static LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createClothesCommand() {
|
||||
return Commands.literal("clothes")
|
||||
// /tiedup clothes url set <url>
|
||||
// /tiedup clothes url reset
|
||||
.then(
|
||||
Commands.literal("url")
|
||||
.then(
|
||||
Commands.literal("set").then(
|
||||
Commands.argument(
|
||||
"url",
|
||||
StringArgumentType.greedyString()
|
||||
).executes(ClothesCommand::setUrl)
|
||||
)
|
||||
)
|
||||
.then(
|
||||
Commands.literal("reset").executes(
|
||||
ClothesCommand::resetUrl
|
||||
)
|
||||
)
|
||||
)
|
||||
// /tiedup clothes fullskin
|
||||
.then(
|
||||
Commands.literal("fullskin").executes(
|
||||
ClothesCommand::toggleFullSkin
|
||||
)
|
||||
)
|
||||
// /tiedup clothes smallarms
|
||||
.then(
|
||||
Commands.literal("smallarms").executes(
|
||||
ClothesCommand::toggleSmallArms
|
||||
)
|
||||
)
|
||||
// /tiedup clothes keephead
|
||||
.then(
|
||||
Commands.literal("keephead").executes(
|
||||
ClothesCommand::toggleKeepHead
|
||||
)
|
||||
)
|
||||
// /tiedup clothes layer <part>
|
||||
.then(
|
||||
Commands.literal("layer").then(
|
||||
Commands.argument("part", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> {
|
||||
builder.suggest("head");
|
||||
builder.suggest("body");
|
||||
builder.suggest("leftarm");
|
||||
builder.suggest("rightarm");
|
||||
builder.suggest("leftleg");
|
||||
builder.suggest("rightleg");
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.executes(ClothesCommand::toggleLayer)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clothes ItemStack from player's main hand.
|
||||
* Returns null and sends error message if not holding clothes.
|
||||
*/
|
||||
private static ItemStack getHeldClothes(
|
||||
CommandContext<CommandSourceStack> ctx
|
||||
) throws CommandSyntaxException {
|
||||
ServerPlayer player = ctx.getSource().getPlayerOrException();
|
||||
ItemStack held = player.getMainHandItem();
|
||||
|
||||
if (held.isEmpty() || !(held.getItem() instanceof GenericClothes)) {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.translatable("command.tiedup.clothes.not_holding")
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return held;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup clothes url set <url>
|
||||
*/
|
||||
private static int setUrl(CommandContext<CommandSourceStack> ctx)
|
||||
throws CommandSyntaxException {
|
||||
ItemStack clothes = getHeldClothes(ctx);
|
||||
if (clothes == null) return 0;
|
||||
|
||||
String url = StringArgumentType.getString(ctx, "url");
|
||||
|
||||
// Basic URL validation
|
||||
if (!url.startsWith("https://")) {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.translatable(
|
||||
"command.tiedup.clothes.url_must_https"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check URL length
|
||||
if (url.length() > 2048) {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.translatable(
|
||||
"command.tiedup.clothes.url_too_long"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GenericClothes item = (GenericClothes) clothes.getItem();
|
||||
item.setDynamicTextureUrl(clothes, url);
|
||||
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() -> Component.translatable("command.tiedup.clothes.url_set"),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup clothes url reset
|
||||
*/
|
||||
private static int resetUrl(CommandContext<CommandSourceStack> ctx)
|
||||
throws CommandSyntaxException {
|
||||
ItemStack clothes = getHeldClothes(ctx);
|
||||
if (clothes == null) return 0;
|
||||
|
||||
GenericClothes item = (GenericClothes) clothes.getItem();
|
||||
item.removeDynamicTextureUrl(clothes);
|
||||
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.translatable("command.tiedup.clothes.url_reset"),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup clothes fullskin
|
||||
*/
|
||||
private static int toggleFullSkin(CommandContext<CommandSourceStack> ctx)
|
||||
throws CommandSyntaxException {
|
||||
ItemStack clothes = getHeldClothes(ctx);
|
||||
if (clothes == null) return 0;
|
||||
|
||||
GenericClothes item = (GenericClothes) clothes.getItem();
|
||||
boolean newState = !item.isFullSkinEnabled(clothes);
|
||||
item.setFullSkinEnabled(clothes, newState);
|
||||
|
||||
String stateKey = newState ? "enabled" : "disabled";
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.translatable(
|
||||
"command.tiedup.clothes.fullskin_" + stateKey
|
||||
),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup clothes smallarms
|
||||
*/
|
||||
private static int toggleSmallArms(CommandContext<CommandSourceStack> ctx)
|
||||
throws CommandSyntaxException {
|
||||
ItemStack clothes = getHeldClothes(ctx);
|
||||
if (clothes == null) return 0;
|
||||
|
||||
GenericClothes item = (GenericClothes) clothes.getItem();
|
||||
boolean newState = !item.shouldForceSmallArms(clothes);
|
||||
item.setForceSmallArms(clothes, newState);
|
||||
|
||||
String stateKey = newState ? "enabled" : "disabled";
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.translatable(
|
||||
"command.tiedup.clothes.smallarms_" + stateKey
|
||||
),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup clothes keephead
|
||||
* When enabled, the wearer's head/hat is preserved instead of being replaced by clothes.
|
||||
*/
|
||||
private static int toggleKeepHead(CommandContext<CommandSourceStack> ctx)
|
||||
throws CommandSyntaxException {
|
||||
ItemStack clothes = getHeldClothes(ctx);
|
||||
if (clothes == null) return 0;
|
||||
|
||||
GenericClothes item = (GenericClothes) clothes.getItem();
|
||||
boolean newState = !item.isKeepHeadEnabled(clothes);
|
||||
item.setKeepHeadEnabled(clothes, newState);
|
||||
|
||||
String stateKey = newState ? "enabled" : "disabled";
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.translatable(
|
||||
"command.tiedup.clothes.keephead_" + stateKey
|
||||
),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup clothes layer <part>
|
||||
*/
|
||||
private static int toggleLayer(CommandContext<CommandSourceStack> ctx)
|
||||
throws CommandSyntaxException {
|
||||
ItemStack clothes = getHeldClothes(ctx);
|
||||
if (clothes == null) return 0;
|
||||
|
||||
String part = StringArgumentType.getString(ctx, "part").toLowerCase();
|
||||
String layerKey = mapPartToLayerKey(part);
|
||||
|
||||
if (layerKey == null) {
|
||||
ctx
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.translatable(
|
||||
"command.tiedup.clothes.unknown_layer",
|
||||
part
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GenericClothes item = (GenericClothes) clothes.getItem();
|
||||
boolean newState = !item.isLayerEnabled(clothes, layerKey);
|
||||
item.setLayerEnabled(clothes, layerKey, newState);
|
||||
|
||||
String stateKey = newState ? "visible" : "hidden";
|
||||
ctx
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.translatable(
|
||||
"command.tiedup.clothes.layer_" + stateKey,
|
||||
part
|
||||
),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map user-friendly part name to NBT layer key.
|
||||
*/
|
||||
private static String mapPartToLayerKey(String part) {
|
||||
return switch (part) {
|
||||
case "head" -> GenericClothes.LAYER_HEAD;
|
||||
case "body" -> GenericClothes.LAYER_BODY;
|
||||
case "leftarm" -> GenericClothes.LAYER_LEFT_ARM;
|
||||
case "rightarm" -> GenericClothes.LAYER_RIGHT_ARM;
|
||||
case "leftleg" -> GenericClothes.LAYER_LEFT_LEG;
|
||||
case "rightleg" -> GenericClothes.LAYER_RIGHT_LEG;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
526
src/main/java/com/tiedup/remake/commands/CollarCommand.java
Normal file
526
src/main/java/com/tiedup/remake/commands/CollarCommand.java
Normal file
@@ -0,0 +1,526 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.cells.CellDataV2;
|
||||
import com.tiedup.remake.cells.CellRegistryV2;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.util.teleport.Position;
|
||||
import com.tiedup.remake.util.teleport.TeleportHelper;
|
||||
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.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Collar management commands for Phase 18.
|
||||
*
|
||||
* Commands:
|
||||
* /collar claim <player> - Claim ownership of a player's collar
|
||||
* /collar unclaim <player> - Remove your ownership
|
||||
* /collar rename <player> <name> - Set alias for collar
|
||||
* /collar addowner <target> <owner> - Add another owner
|
||||
* /collar removeowner <target> <owner> - Remove an owner
|
||||
* /collar sethome <player> - Set home location
|
||||
* /collar setprison <player> - Set prison location (legacy)
|
||||
* /collar setcell <player> <cellname> - Assign cell to collar (preferred)
|
||||
* /collar prisonradius <player> <radius> - Set prison fence radius (legacy)
|
||||
* /collar prisonfence <player> [on/off] - Toggle prison fence (legacy)
|
||||
* /collar backhome <player> - Teleport slave back home
|
||||
* /collar info <player> - Show collar info
|
||||
*
|
||||
* Requires OP level 2.
|
||||
*/
|
||||
public class CollarCommand {
|
||||
|
||||
public static void register(
|
||||
CommandDispatcher<CommandSourceStack> dispatcher
|
||||
) {
|
||||
dispatcher.register(createCollarCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the collar command builder (for use as subcommand of /tiedup).
|
||||
* @return The command builder
|
||||
*/
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createCollarCommand() {
|
||||
return Commands.literal("collar")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
// /collar claim <player>
|
||||
.then(
|
||||
Commands.literal("claim").then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommand::claim)
|
||||
)
|
||||
)
|
||||
// /collar unclaim <player>
|
||||
.then(
|
||||
Commands.literal("unclaim").then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommand::unclaim)
|
||||
)
|
||||
)
|
||||
// /collar rename <player> <name>
|
||||
.then(
|
||||
Commands.literal("rename").then(
|
||||
Commands.argument("player", EntityArgument.player()).then(
|
||||
Commands.argument(
|
||||
"name",
|
||||
StringArgumentType.greedyString()
|
||||
).executes(CollarCommand::rename)
|
||||
)
|
||||
)
|
||||
)
|
||||
// /collar addowner <target> <owner>
|
||||
.then(
|
||||
Commands.literal("addowner").then(
|
||||
Commands.argument("target", EntityArgument.player()).then(
|
||||
Commands.argument(
|
||||
"owner",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommand::addOwner)
|
||||
)
|
||||
)
|
||||
)
|
||||
// /collar removeowner <target> <owner>
|
||||
.then(
|
||||
Commands.literal("removeowner").then(
|
||||
Commands.argument("target", EntityArgument.player()).then(
|
||||
Commands.argument(
|
||||
"owner",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommand::removeOwner)
|
||||
)
|
||||
)
|
||||
)
|
||||
// /collar setcell <player> <cellname> (assigns cell)
|
||||
.then(
|
||||
Commands.literal("setcell").then(
|
||||
Commands.argument("player", EntityArgument.player()).then(
|
||||
Commands.argument(
|
||||
"cellname",
|
||||
StringArgumentType.word()
|
||||
).executes(CollarCommand::setCell)
|
||||
)
|
||||
)
|
||||
)
|
||||
// /collar tocell <player> (teleport to assigned cell)
|
||||
.then(
|
||||
Commands.literal("tocell").then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommand::teleportToCell)
|
||||
)
|
||||
)
|
||||
// /collar info <player>
|
||||
.then(
|
||||
Commands.literal("info").then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(CollarCommand::info)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack getPlayerCollar(ServerPlayer player) {
|
||||
PlayerBindState state = PlayerBindState.getInstance(player);
|
||||
if (state == null || !state.hasCollar()) return ItemStack.EMPTY;
|
||||
return state.getEquipment(BodyRegionV2.NECK);
|
||||
}
|
||||
|
||||
private static int claim(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
|
||||
ItemStack collar = getPlayerCollar(target);
|
||||
if (collar.isEmpty()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " does not have a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (source.getEntity() instanceof ServerPlayer executor) {
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
collarItem.addOwner(collar, executor);
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aClaimed " +
|
||||
target.getName().getString() +
|
||||
"'s collar"
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
source.sendFailure(Component.literal("Failed to claim collar"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int unclaim(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
|
||||
ItemStack collar = getPlayerCollar(target);
|
||||
if (collar.isEmpty()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " does not have a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (source.getEntity() instanceof ServerPlayer executor) {
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
collarItem.removeOwner(collar, executor.getUUID());
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aRemoved your ownership from " +
|
||||
target.getName().getString() +
|
||||
"'s collar"
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
source.sendFailure(Component.literal("Failed to unclaim collar"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int rename(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
String name = StringArgumentType.getString(context, "name");
|
||||
|
||||
ItemStack collar = getPlayerCollar(target);
|
||||
if (collar.isEmpty()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " does not have a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
collarItem.setNickname(collar, name);
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aSet collar nickname to '" + name + "'"
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int addOwner(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "target");
|
||||
ServerPlayer owner = EntityArgument.getPlayer(context, "owner");
|
||||
|
||||
ItemStack collar = getPlayerCollar(target);
|
||||
if (collar.isEmpty()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " does not have a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
collarItem.addOwner(collar, owner);
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aAdded " +
|
||||
owner.getName().getString() +
|
||||
" as owner of " +
|
||||
target.getName().getString() +
|
||||
"'s collar"
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int removeOwner(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "target");
|
||||
ServerPlayer owner = EntityArgument.getPlayer(context, "owner");
|
||||
|
||||
ItemStack collar = getPlayerCollar(target);
|
||||
if (collar.isEmpty()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " does not have a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
collarItem.removeOwner(collar, owner.getUUID());
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aRemoved " +
|
||||
owner.getName().getString() +
|
||||
" as owner of " +
|
||||
target.getName().getString() +
|
||||
"'s collar"
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* /collar setcell <player> <cellname>
|
||||
*
|
||||
* Assign a named cell to a player's collar.
|
||||
*/
|
||||
private static int setCell(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
String cellName = StringArgumentType.getString(context, "cellname");
|
||||
|
||||
ItemStack collar = getPlayerCollar(target);
|
||||
if (collar.isEmpty()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " does not have a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the cell by name
|
||||
ServerLevel serverLevel = source.getLevel();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
CellDataV2 cell = registry.getCellByName(cellName);
|
||||
|
||||
if (cell == null) {
|
||||
source.sendFailure(
|
||||
Component.literal("Cell '" + cellName + "' not found")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
collarItem.setCellId(collar, cell.getId());
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aAssigned cell '" +
|
||||
cellName +
|
||||
"' to " +
|
||||
target.getName().getString() +
|
||||
"'s collar"
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* /collar tocell <player>
|
||||
*
|
||||
* Teleport player to their assigned cell.
|
||||
*/
|
||||
private static int teleportToCell(
|
||||
CommandContext<CommandSourceStack> context
|
||||
) throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
|
||||
ItemStack collar = getPlayerCollar(target);
|
||||
if (collar.isEmpty()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " does not have a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
if (!collarItem.hasCellAssigned(collar)) {
|
||||
source.sendFailure(
|
||||
Component.literal("No cell assigned to collar")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get cell position and teleport
|
||||
java.util.UUID cellId = collarItem.getCellId(collar);
|
||||
ServerLevel serverLevel = source.getLevel();
|
||||
CellDataV2 cell = CellRegistryV2.get(serverLevel).getCell(cellId);
|
||||
|
||||
if (cell == null) {
|
||||
source.sendFailure(
|
||||
Component.literal("Assigned cell no longer exists")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
net.minecraft.core.BlockPos teleportTarget =
|
||||
cell.getSpawnPoint() != null
|
||||
? cell.getSpawnPoint()
|
||||
: cell.getCorePos().above();
|
||||
Position cellPos = new Position(
|
||||
teleportTarget,
|
||||
serverLevel.dimension()
|
||||
);
|
||||
TeleportHelper.teleportEntity(target, cellPos);
|
||||
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aTeleported " +
|
||||
target.getName().getString() +
|
||||
" to cell at " +
|
||||
cell.getCorePos().toShortString()
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int info(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
|
||||
ItemStack collar = getPlayerCollar(target);
|
||||
if (collar.isEmpty()) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " does not have a collar"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§6=== Collar Info for " +
|
||||
target.getName().getString() +
|
||||
" ==="
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
String nickname = collarItem.getNickname(collar);
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§7Nickname: §f" +
|
||||
(nickname.isEmpty() ? "None" : nickname)
|
||||
),
|
||||
false
|
||||
);
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§7Has Owner: §f" + collarItem.hasOwner(collar)
|
||||
),
|
||||
false
|
||||
);
|
||||
// Cell assignment
|
||||
java.util.UUID cellId = collarItem.getCellId(collar);
|
||||
if (cellId != null) {
|
||||
ServerLevel serverLevel = source.getLevel();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
||||
CellDataV2 cell = registry.getCell(cellId);
|
||||
if (cell != null) {
|
||||
String cellDisplay =
|
||||
cell.getName() != null
|
||||
? cell.getName()
|
||||
: cellId.toString().substring(0, 8) + "...";
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§7Assigned Cell: §a" +
|
||||
cellDisplay +
|
||||
" §7@ " +
|
||||
cell.getCorePos().toShortString()
|
||||
),
|
||||
false
|
||||
);
|
||||
} else {
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("§7Assigned Cell: §c(deleted)"),
|
||||
false
|
||||
);
|
||||
}
|
||||
} else {
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("§7Assigned Cell: §fNone"),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§7Locked: §f" + collarItem.isLocked(collar)
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
91
src/main/java/com/tiedup/remake/commands/CommandHelper.java
Normal file
91
src/main/java/com/tiedup/remake/commands/CommandHelper.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.sync.PacketSyncBindState;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
/**
|
||||
* Utility methods for command handling.
|
||||
*/
|
||||
public final class CommandHelper {
|
||||
|
||||
/**
|
||||
* Permission level 2 (OP) requirement for commands.
|
||||
* Use with: .requires(CommandHelper.REQUIRES_OP)
|
||||
*/
|
||||
public static final Predicate<CommandSourceStack> REQUIRES_OP = source ->
|
||||
source.hasPermission(2);
|
||||
|
||||
private CommandHelper() {}
|
||||
|
||||
/**
|
||||
* Get the player from command source, or send failure message and return empty.
|
||||
*
|
||||
* Usage:
|
||||
* <pre>
|
||||
* var playerOpt = CommandHelper.getPlayerOrFail(source);
|
||||
* if (playerOpt.isEmpty()) return 0;
|
||||
* ServerPlayer player = playerOpt.get();
|
||||
* </pre>
|
||||
*
|
||||
* @param source The command source
|
||||
* @return Optional containing the player, or empty if source is not a player
|
||||
*/
|
||||
public static Optional<ServerPlayer> getPlayerOrFail(
|
||||
CommandSourceStack source
|
||||
) {
|
||||
if (source.getEntity() instanceof ServerPlayer player) {
|
||||
return Optional.of(player);
|
||||
}
|
||||
source.sendFailure(Component.literal("Must be a player"));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player from command source without sending failure message.
|
||||
*
|
||||
* @param source The command source
|
||||
* @return Optional containing the player, or empty if source is not a player
|
||||
*/
|
||||
public static Optional<ServerPlayer> getPlayer(CommandSourceStack source) {
|
||||
if (source.getEntity() instanceof ServerPlayer player) {
|
||||
return Optional.of(player);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if source is a player.
|
||||
*
|
||||
* @param source The command source
|
||||
* @return true if source is a player
|
||||
*/
|
||||
public static boolean isPlayer(CommandSourceStack source) {
|
||||
return source.getEntity() instanceof ServerPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync player state to client after command changes.
|
||||
*
|
||||
* @param player The player to sync
|
||||
* @param state The player's bind state
|
||||
*/
|
||||
public static void syncPlayerState(
|
||||
ServerPlayer player,
|
||||
PlayerBindState state
|
||||
) {
|
||||
// Sync V2 equipment
|
||||
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(player);
|
||||
|
||||
// Sync bind state
|
||||
PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(player);
|
||||
if (statePacket != null) {
|
||||
ModNetwork.sendToPlayer(statePacket, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
294
src/main/java/com/tiedup/remake/commands/KeyCommand.java
Normal file
294
src/main/java/com/tiedup/remake/commands/KeyCommand.java
Normal file
@@ -0,0 +1,294 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import java.util.Optional;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Key management commands for Phase 18.
|
||||
*
|
||||
* Commands:
|
||||
* /key claim - Claim the key you're holding
|
||||
* /key unclaim - Remove ownership from your key
|
||||
* /key assign <player> - Assign your key to a player
|
||||
* /key public - Make key publicly usable
|
||||
* /key info - Show key info
|
||||
*
|
||||
* Must hold a collar key in main hand.
|
||||
*/
|
||||
public class KeyCommand {
|
||||
|
||||
private static final String TAG_OWNER = "Owner";
|
||||
private static final String TAG_OWNER_NAME = "OwnerName";
|
||||
private static final String TAG_TARGET = "Target";
|
||||
private static final String TAG_TARGET_NAME = "TargetName";
|
||||
private static final String TAG_PUBLIC = "Public";
|
||||
|
||||
public static void register(
|
||||
CommandDispatcher<CommandSourceStack> dispatcher
|
||||
) {
|
||||
dispatcher.register(createKeyCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the key command builder (for use as subcommand of /tiedup).
|
||||
* @return The command builder
|
||||
*/
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createKeyCommand() {
|
||||
return Commands.literal("key")
|
||||
// /key claim
|
||||
.then(Commands.literal("claim").executes(KeyCommand::claim))
|
||||
// /key unclaim
|
||||
.then(Commands.literal("unclaim").executes(KeyCommand::unclaim))
|
||||
// /key assign <player>
|
||||
.then(
|
||||
Commands.literal("assign").then(
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(KeyCommand::assign)
|
||||
)
|
||||
)
|
||||
// /key public
|
||||
.then(Commands.literal("public").executes(KeyCommand::togglePublic))
|
||||
// /key info
|
||||
.then(Commands.literal("info").executes(KeyCommand::info));
|
||||
}
|
||||
|
||||
private static ItemStack getHeldKey(ServerPlayer player) {
|
||||
ItemStack held = player.getMainHandItem();
|
||||
if (
|
||||
held.is(ModItems.COLLAR_KEY.get()) ||
|
||||
held.is(ModItems.MASTER_KEY.get())
|
||||
) {
|
||||
return held;
|
||||
}
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
private static int claim(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
ItemStack key = getHeldKey(player);
|
||||
if (key.isEmpty()) {
|
||||
source.sendFailure(Component.literal("You must hold a collar key"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
CompoundTag tag = key.getOrCreateTag();
|
||||
|
||||
// Check if already claimed by someone else
|
||||
if (
|
||||
tag.hasUUID(TAG_OWNER) &&
|
||||
!tag.getUUID(TAG_OWNER).equals(player.getUUID())
|
||||
) {
|
||||
source.sendFailure(
|
||||
Component.literal("This key is already claimed by someone else")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tag.putUUID(TAG_OWNER, player.getUUID());
|
||||
tag.putString(TAG_OWNER_NAME, player.getName().getString());
|
||||
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("§aYou have claimed this key"),
|
||||
false
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int unclaim(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
ItemStack key = getHeldKey(player);
|
||||
if (key.isEmpty()) {
|
||||
source.sendFailure(Component.literal("You must hold a collar key"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
CompoundTag tag = key.getOrCreateTag();
|
||||
|
||||
if (!tag.hasUUID(TAG_OWNER)) {
|
||||
source.sendFailure(Component.literal("This key is not claimed"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!tag.getUUID(TAG_OWNER).equals(player.getUUID())) {
|
||||
source.sendFailure(Component.literal("You do not own this key"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
tag.remove(TAG_OWNER);
|
||||
tag.remove(TAG_OWNER_NAME);
|
||||
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("§aYou have unclaimed this key"),
|
||||
false
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int assign(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
|
||||
ItemStack key = getHeldKey(player);
|
||||
if (key.isEmpty()) {
|
||||
source.sendFailure(Component.literal("You must hold a collar key"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
CompoundTag tag = key.getOrCreateTag();
|
||||
|
||||
// Must be owner to assign
|
||||
if (
|
||||
tag.hasUUID(TAG_OWNER) &&
|
||||
!tag.getUUID(TAG_OWNER).equals(player.getUUID())
|
||||
) {
|
||||
source.sendFailure(Component.literal("You do not own this key"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
tag.putUUID(TAG_TARGET, target.getUUID());
|
||||
tag.putString(TAG_TARGET_NAME, target.getName().getString());
|
||||
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aAssigned key to " + target.getName().getString()
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int togglePublic(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
ItemStack key = getHeldKey(player);
|
||||
if (key.isEmpty()) {
|
||||
source.sendFailure(Component.literal("You must hold a collar key"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
CompoundTag tag = key.getOrCreateTag();
|
||||
|
||||
// Must be owner to toggle
|
||||
if (
|
||||
tag.hasUUID(TAG_OWNER) &&
|
||||
!tag.getUUID(TAG_OWNER).equals(player.getUUID())
|
||||
) {
|
||||
source.sendFailure(Component.literal("You do not own this key"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean isPublic = !tag.getBoolean(TAG_PUBLIC);
|
||||
tag.putBoolean(TAG_PUBLIC, isPublic);
|
||||
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aKey is now " + (isPublic ? "public" : "private")
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int info(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
ItemStack key = getHeldKey(player);
|
||||
if (key.isEmpty()) {
|
||||
source.sendFailure(Component.literal("You must hold a collar key"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
CompoundTag tag = key.getOrCreateTag();
|
||||
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("§6=== Key Info ==="),
|
||||
false
|
||||
);
|
||||
|
||||
String ownerName = tag.getString(TAG_OWNER_NAME);
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§7Owner: §f" +
|
||||
(ownerName.isEmpty() ? "Not claimed" : ownerName)
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
String targetName = tag.getString(TAG_TARGET_NAME);
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§7Assigned to: §f" +
|
||||
(targetName.isEmpty() ? "Not assigned" : targetName)
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
boolean isPublic = tag.getBoolean(TAG_PUBLIC);
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("§7Public: §f" + (isPublic ? "Yes" : "No")),
|
||||
false
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
234
src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java
Normal file
234
src/main/java/com/tiedup/remake/commands/KidnapSetCommand.java
Normal file
@@ -0,0 +1,234 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
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.items.base.KnifeVariant;
|
||||
import java.util.Optional;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Utility commands for Phase 18.
|
||||
*
|
||||
* Commands:
|
||||
* /kidnapset - Get a starter kit of mod items
|
||||
* /kidnapreload [type] - Reload data files (jobs, sales, gagtalk)
|
||||
*/
|
||||
public class KidnapSetCommand {
|
||||
|
||||
public static void register(
|
||||
CommandDispatcher<CommandSourceStack> dispatcher
|
||||
) {
|
||||
dispatcher.register(createKidnapSetCommand());
|
||||
dispatcher.register(createKidnapReloadCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the kidnapset command builder (for use as subcommand of /tiedup).
|
||||
* @return The command builder
|
||||
*/
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createKidnapSetCommand() {
|
||||
return Commands.literal("kidnapset")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.executes(KidnapSetCommand::giveSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the kidnapreload command builder (for use as subcommand of /tiedup).
|
||||
* @return The command builder
|
||||
*/
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createKidnapReloadCommand() {
|
||||
return Commands.literal("kidnapreload")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.executes(ctx -> reload(ctx, "all"))
|
||||
.then(Commands.literal("jobs").executes(ctx -> reload(ctx, "jobs")))
|
||||
.then(
|
||||
Commands.literal("sales").executes(ctx -> reload(ctx, "sales"))
|
||||
)
|
||||
.then(
|
||||
Commands.literal("gagtalk").executes(ctx ->
|
||||
reload(ctx, "gagtalk")
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("all").executes(ctx -> reload(ctx, "all")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a starter kit of mod items to the player.
|
||||
*/
|
||||
private static int giveSet(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
int given = 0;
|
||||
|
||||
// Binds
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getBind(BindVariant.ROPES), 8)
|
||||
);
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getBind(BindVariant.CHAIN), 4)
|
||||
);
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getBind(BindVariant.LEATHER_STRAPS), 4)
|
||||
);
|
||||
|
||||
// Gags
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG), 4)
|
||||
);
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getGag(GagVariant.BALL_GAG), 4)
|
||||
);
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getGag(GagVariant.TAPE_GAG), 4)
|
||||
);
|
||||
|
||||
// Blindfolds
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC), 4)
|
||||
);
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.MASK), 2)
|
||||
);
|
||||
|
||||
// Collars
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.CLASSIC_COLLAR.get(), 4)
|
||||
);
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.SHOCK_COLLAR.get(), 2)
|
||||
);
|
||||
given += giveItem(player, new ItemStack(ModItems.GPS_COLLAR.get(), 2));
|
||||
|
||||
// Tools
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getKnife(KnifeVariant.IRON), 2)
|
||||
);
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getKnife(KnifeVariant.GOLDEN), 1)
|
||||
);
|
||||
given += giveItem(player, new ItemStack(ModItems.WHIP.get(), 1));
|
||||
given += giveItem(player, new ItemStack(ModItems.PADDLE.get(), 1));
|
||||
|
||||
// Controllers
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.SHOCKER_CONTROLLER.get(), 1)
|
||||
);
|
||||
given += giveItem(player, new ItemStack(ModItems.GPS_LOCATOR.get(), 1));
|
||||
|
||||
// Keys and locks
|
||||
given += giveItem(player, new ItemStack(ModItems.PADLOCK.get(), 4));
|
||||
given += giveItem(player, new ItemStack(ModItems.COLLAR_KEY.get(), 2));
|
||||
given += giveItem(player, new ItemStack(ModItems.MASTER_KEY.get(), 1));
|
||||
|
||||
// Earplugs
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.getEarplugs(EarplugsVariant.CLASSIC), 4)
|
||||
);
|
||||
|
||||
// Rope arrows
|
||||
given += giveItem(player, new ItemStack(ModItems.ROPE_ARROW.get(), 16));
|
||||
|
||||
// Chloroform
|
||||
given += giveItem(
|
||||
player,
|
||||
new ItemStack(ModItems.CHLOROFORM_BOTTLE.get(), 2)
|
||||
);
|
||||
given += giveItem(player, new ItemStack(ModItems.RAG.get(), 4));
|
||||
|
||||
int finalGiven = given;
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aGave kidnap set (" + finalGiven + " item stacks)"
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
return finalGiven;
|
||||
}
|
||||
|
||||
private static int giveItem(ServerPlayer player, ItemStack stack) {
|
||||
if (!player.getInventory().add(stack)) {
|
||||
// Drop on ground if inventory full
|
||||
player.drop(stack, false);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload data files.
|
||||
*/
|
||||
private static int reload(
|
||||
CommandContext<CommandSourceStack> context,
|
||||
String type
|
||||
) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
int reloaded = 0;
|
||||
|
||||
if (type.equals("all") || type.equals("jobs")) {
|
||||
com.tiedup.remake.util.tasks.JobLoader.init();
|
||||
reloaded++;
|
||||
}
|
||||
|
||||
if (type.equals("all") || type.equals("sales")) {
|
||||
com.tiedup.remake.util.tasks.SaleLoader.init();
|
||||
reloaded++;
|
||||
}
|
||||
|
||||
if (type.equals("all") || type.equals("gagtalk")) {
|
||||
// GagTalkManager is code-based (no external data files)
|
||||
// No reload needed - materials/logic defined in GagMaterial enum
|
||||
reloaded++;
|
||||
}
|
||||
|
||||
int finalReloaded = reloaded;
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aReloaded " +
|
||||
(type.equals("all") ? "all data files" : type) +
|
||||
" (" +
|
||||
finalReloaded +
|
||||
" files)"
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
return reloaded;
|
||||
}
|
||||
}
|
||||
754
src/main/java/com/tiedup/remake/commands/NPCCommand.java
Normal file
754
src/main/java/com/tiedup/remake/commands/NPCCommand.java
Normal file
@@ -0,0 +1,754 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
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 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 for Phase 18.
|
||||
*
|
||||
* 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.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<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.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<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.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<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.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<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.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<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.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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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<CommandSourceStack> 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;
|
||||
}
|
||||
}
|
||||
511
src/main/java/com/tiedup/remake/commands/SocialCommand.java
Normal file
511
src/main/java/com/tiedup/remake/commands/SocialCommand.java
Normal file
@@ -0,0 +1,511 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.SocialData;
|
||||
import com.tiedup.remake.util.MessageDispatcher;
|
||||
import java.util.*;
|
||||
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.phys.AABB;
|
||||
|
||||
/**
|
||||
* Social and RP commands for Phase 18.
|
||||
*
|
||||
* Commands:
|
||||
* /blockplayer <player> - Block a player from interacting with you
|
||||
* /unblockplayer <player> - Unblock a player
|
||||
* /checkblocked <player> - Check if a player has blocked you
|
||||
* /norp - Announce non-consent to current RP (cooldown 45s)
|
||||
* /me <action> - Roleplay action message in local area
|
||||
* /pm <player> <message> - Private message to a player
|
||||
* /talkarea [distance] - Set local chat area distance
|
||||
*
|
||||
* These commands are usable while tied up.
|
||||
*
|
||||
* Data persistence: Block lists and talk area settings are stored in SocialData
|
||||
* (SavedData) and persist across server restarts.
|
||||
*/
|
||||
public class SocialCommand {
|
||||
|
||||
// Cooldowns for /norp (UUID -> last use timestamp)
|
||||
// Note: Cooldowns are intentionally NOT persisted - they reset on server restart
|
||||
private static final Map<UUID, Long> NORP_COOLDOWNS = new HashMap<>();
|
||||
private static final long NORP_COOLDOWN_MS = 45000; // 45 seconds
|
||||
|
||||
/** Remove player cooldown on disconnect to prevent memory leak. */
|
||||
public static void cleanupPlayer(UUID playerId) {
|
||||
NORP_COOLDOWNS.remove(playerId);
|
||||
}
|
||||
|
||||
public static void register(
|
||||
CommandDispatcher<CommandSourceStack> dispatcher
|
||||
) {
|
||||
dispatcher.register(createBlockPlayerCommand());
|
||||
dispatcher.register(createUnblockPlayerCommand());
|
||||
dispatcher.register(createCheckBlockedCommand());
|
||||
dispatcher.register(createNoRPCommand());
|
||||
dispatcher.register(createMeCommand());
|
||||
dispatcher.register(createPMCommand());
|
||||
dispatcher.register(createTalkAreaCommand());
|
||||
dispatcher.register(createTalkInfoCommand());
|
||||
}
|
||||
|
||||
// === Command Builders (for use as subcommands of /tiedup) ===
|
||||
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createBlockPlayerCommand() {
|
||||
return Commands.literal("blockplayer").then(
|
||||
Commands.argument("player", EntityArgument.player()).executes(
|
||||
SocialCommand::blockPlayer
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createUnblockPlayerCommand() {
|
||||
return Commands.literal("unblockplayer").then(
|
||||
Commands.argument("player", EntityArgument.player()).executes(
|
||||
SocialCommand::unblockPlayer
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createCheckBlockedCommand() {
|
||||
return Commands.literal("checkblocked").then(
|
||||
Commands.argument("player", EntityArgument.player()).executes(
|
||||
SocialCommand::checkBlocked
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createNoRPCommand() {
|
||||
return Commands.literal("norp").executes(SocialCommand::noRP);
|
||||
}
|
||||
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createMeCommand() {
|
||||
return Commands.literal("me").then(
|
||||
Commands.argument(
|
||||
"action",
|
||||
StringArgumentType.greedyString()
|
||||
).executes(SocialCommand::meAction)
|
||||
);
|
||||
}
|
||||
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createPMCommand() {
|
||||
return Commands.literal("pm").then(
|
||||
Commands.argument("player", EntityArgument.player()).then(
|
||||
Commands.argument(
|
||||
"message",
|
||||
StringArgumentType.greedyString()
|
||||
).executes(SocialCommand::privateMessage)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createTalkAreaCommand() {
|
||||
return Commands.literal("talkarea")
|
||||
.executes(ctx -> setTalkArea(ctx, 0)) // Disable
|
||||
.then(
|
||||
Commands.argument(
|
||||
"distance",
|
||||
IntegerArgumentType.integer(1, 100)
|
||||
).executes(ctx ->
|
||||
setTalkArea(
|
||||
ctx,
|
||||
IntegerArgumentType.getInteger(ctx, "distance")
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
|
||||
CommandSourceStack
|
||||
> createTalkInfoCommand() {
|
||||
return Commands.literal("talkinfo").executes(SocialCommand::talkInfo);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Block System
|
||||
// ========================================
|
||||
|
||||
private static int blockPlayer(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
|
||||
if (player.getUUID().equals(target.getUUID())) {
|
||||
source.sendFailure(Component.literal("You cannot block yourself"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
SocialData data = SocialData.get(player.serverLevel());
|
||||
|
||||
if (data.isBlocked(player.getUUID(), target.getUUID())) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " is already blocked"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
data.addBlock(player.getUUID(), target.getUUID());
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal("§aBlocked " + target.getName().getString()),
|
||||
false
|
||||
);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[SOCIAL] {} blocked {}",
|
||||
player.getName().getString(),
|
||||
target.getName().getString()
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int unblockPlayer(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
|
||||
SocialData data = SocialData.get(player.serverLevel());
|
||||
|
||||
if (!data.isBlocked(player.getUUID(), target.getUUID())) {
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " is not blocked"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
data.removeBlock(player.getUUID(), target.getUUID());
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aUnblocked " + target.getName().getString()
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int checkBlocked(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
|
||||
SocialData data = SocialData.get(player.serverLevel());
|
||||
boolean blocked = data.isBlocked(target.getUUID(), player.getUUID());
|
||||
|
||||
if (blocked) {
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§c" + target.getName().getString() + " has blocked you"
|
||||
),
|
||||
false
|
||||
);
|
||||
} else {
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§a" +
|
||||
target.getName().getString() +
|
||||
" has not blocked you"
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player is blocked by another.
|
||||
* Can be used by other systems to check interaction permissions.
|
||||
*
|
||||
* @param level The server level to get SocialData from
|
||||
* @param blocker The player who may have blocked
|
||||
* @param blocked The player who may be blocked
|
||||
* @return true if blocker has blocked blocked
|
||||
*/
|
||||
public static boolean isBlocked(
|
||||
ServerLevel level,
|
||||
UUID blocker,
|
||||
UUID blocked
|
||||
) {
|
||||
return SocialData.get(level).isBlocked(blocker, blocked);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// RP Commands
|
||||
// ========================================
|
||||
|
||||
private static int noRP(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
// Check cooldown
|
||||
long now = System.currentTimeMillis();
|
||||
Long lastUse = NORP_COOLDOWNS.get(player.getUUID());
|
||||
if (lastUse != null && now - lastUse < NORP_COOLDOWN_MS) {
|
||||
long remaining = (NORP_COOLDOWN_MS - (now - lastUse)) / 1000;
|
||||
source.sendFailure(
|
||||
Component.literal(
|
||||
"Please wait " +
|
||||
remaining +
|
||||
" seconds before using /norp again"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Set cooldown
|
||||
NORP_COOLDOWNS.put(player.getUUID(), now);
|
||||
|
||||
// Broadcast to all players
|
||||
Component message = Component.literal("")
|
||||
.append(
|
||||
Component.literal("[NoRP] ").withStyle(
|
||||
ChatFormatting.RED,
|
||||
ChatFormatting.BOLD
|
||||
)
|
||||
)
|
||||
.append(
|
||||
Component.literal(player.getName().getString()).withStyle(
|
||||
ChatFormatting.YELLOW
|
||||
)
|
||||
)
|
||||
.append(
|
||||
Component.literal(
|
||||
" has announced non-consent to current RP"
|
||||
).withStyle(ChatFormatting.RED)
|
||||
);
|
||||
|
||||
// Broadcast to all players (earplug-aware)
|
||||
for (ServerPlayer p : player.server.getPlayerList().getPlayers()) {
|
||||
MessageDispatcher.sendTo(p, message);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[SOCIAL] {} used /norp",
|
||||
player.getName().getString()
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int meAction(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
String action = StringArgumentType.getString(context, "action");
|
||||
|
||||
// Get talk area for local chat
|
||||
SocialData data = SocialData.get(player.serverLevel());
|
||||
int talkArea = data.getTalkArea(player.getUUID());
|
||||
|
||||
Component message = Component.literal("")
|
||||
.append(
|
||||
Component.literal("* ").withStyle(ChatFormatting.LIGHT_PURPLE)
|
||||
)
|
||||
.append(
|
||||
Component.literal(player.getName().getString()).withStyle(
|
||||
ChatFormatting.LIGHT_PURPLE
|
||||
)
|
||||
)
|
||||
.append(
|
||||
Component.literal(" " + action).withStyle(
|
||||
ChatFormatting.LIGHT_PURPLE
|
||||
)
|
||||
);
|
||||
|
||||
if (talkArea > 0) {
|
||||
// Local chat - send to nearby players (earplug-aware)
|
||||
AABB area = player.getBoundingBox().inflate(talkArea);
|
||||
List<ServerPlayer> nearby = player
|
||||
.serverLevel()
|
||||
.getEntitiesOfClass(ServerPlayer.class, area);
|
||||
for (ServerPlayer p : nearby) {
|
||||
MessageDispatcher.sendTo(p, message);
|
||||
}
|
||||
} else {
|
||||
// Global chat (earplug-aware)
|
||||
for (ServerPlayer p : player.server.getPlayerList().getPlayers()) {
|
||||
MessageDispatcher.sendTo(p, message);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int privateMessage(
|
||||
CommandContext<CommandSourceStack> context
|
||||
) throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> senderOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (senderOpt.isEmpty()) return 0;
|
||||
ServerPlayer sender = senderOpt.get();
|
||||
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
String message = StringArgumentType.getString(context, "message");
|
||||
|
||||
// Check if blocked
|
||||
SocialData data = SocialData.get(sender.serverLevel());
|
||||
if (data.isBlocked(target.getUUID(), sender.getUUID())) {
|
||||
source.sendFailure(
|
||||
Component.literal("This player has blocked you")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Send to target (earplug-aware)
|
||||
Component toTarget = Component.literal("")
|
||||
.append(
|
||||
Component.literal(
|
||||
"[PM from " + sender.getName().getString() + "] "
|
||||
).withStyle(ChatFormatting.LIGHT_PURPLE)
|
||||
)
|
||||
.append(Component.literal(message).withStyle(ChatFormatting.WHITE));
|
||||
MessageDispatcher.sendFrom(sender, target, toTarget);
|
||||
|
||||
// Confirm to sender (always show - they're the one sending)
|
||||
Component toSender = Component.literal("")
|
||||
.append(
|
||||
Component.literal(
|
||||
"[PM to " + target.getName().getString() + "] "
|
||||
).withStyle(ChatFormatting.GRAY)
|
||||
)
|
||||
.append(Component.literal(message).withStyle(ChatFormatting.WHITE));
|
||||
MessageDispatcher.sendSystemMessage(sender, toSender);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Talk Area
|
||||
// ========================================
|
||||
|
||||
private static int setTalkArea(
|
||||
CommandContext<CommandSourceStack> context,
|
||||
int distance
|
||||
) throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
SocialData data = SocialData.get(player.serverLevel());
|
||||
data.setTalkArea(player.getUUID(), distance);
|
||||
|
||||
if (distance == 0) {
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("§aTalk area disabled (global chat)"),
|
||||
false
|
||||
);
|
||||
} else {
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"§aTalk area set to " + distance + " blocks"
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int talkInfo(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
|
||||
source
|
||||
);
|
||||
if (playerOpt.isEmpty()) return 0;
|
||||
ServerPlayer player = playerOpt.get();
|
||||
|
||||
SocialData data = SocialData.get(player.serverLevel());
|
||||
int talkArea = data.getTalkArea(player.getUUID());
|
||||
|
||||
if (talkArea == 0) {
|
||||
source.sendSuccess(
|
||||
() ->
|
||||
Component.literal("Talk area: §edisabled §7(global chat)"),
|
||||
false
|
||||
);
|
||||
} else {
|
||||
source.sendSuccess(
|
||||
() -> Component.literal("Talk area: §e" + talkArea + " blocks"),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
80
src/main/java/com/tiedup/remake/commands/TiedUpCommand.java
Normal file
80
src/main/java/com/tiedup/remake/commands/TiedUpCommand.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.tiedup.remake.commands.subcommands.BondageSubCommand;
|
||||
import com.tiedup.remake.commands.subcommands.DebtSubCommand;
|
||||
import com.tiedup.remake.commands.subcommands.InventorySubCommand;
|
||||
import com.tiedup.remake.commands.subcommands.MasterTestSubCommand;
|
||||
import com.tiedup.remake.commands.subcommands.TestAnimSubCommand;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
|
||||
/**
|
||||
* Main TiedUp! command suite — registration hub.
|
||||
*
|
||||
* All command implementations are delegated to domain-specific sub-command classes
|
||||
* in the {@code subcommands/} package. This class only builds the root node and
|
||||
* wires together the sub-trees.
|
||||
*
|
||||
* Commands are grouped by domain:
|
||||
* - Bondage: tie, untie, gag, ungag, blindfold, unblind, collar, takecollar,
|
||||
* takeearplugs, putearplugs, takeclothes, putclothes, fullyrestrain, enslave, free, adjust
|
||||
* - Debt: debt show/set/add/remove
|
||||
* - Master test: mastertest, masterchair, mastertask
|
||||
* - Test animation: testanim <animId>/stop
|
||||
* - Inventory: returnstuff
|
||||
* - Plus existing delegated commands: bounty, npc, key, collar, clothes,
|
||||
* kidnapset, kidnapreload, cell, social, debug
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public class TiedUpCommand {
|
||||
|
||||
/**
|
||||
* Register all TiedUp commands with the command dispatcher.
|
||||
*
|
||||
* @param dispatcher The command dispatcher
|
||||
*/
|
||||
public static void register(
|
||||
CommandDispatcher<CommandSourceStack> dispatcher
|
||||
) {
|
||||
var root = Commands.literal("tiedup");
|
||||
|
||||
// === Domain sub-commands (implemented in subcommands/ package) ===
|
||||
BondageSubCommand.register(root);
|
||||
DebtSubCommand.register(root);
|
||||
MasterTestSubCommand.register(root);
|
||||
TestAnimSubCommand.register(root);
|
||||
InventorySubCommand.register(root);
|
||||
|
||||
// === Existing delegated commands (each has its own class) ===
|
||||
// /tiedup bounty <player> - Bounty system
|
||||
root.then(BountyCommand.createBountyCommand());
|
||||
// /tiedup npc ... - NPC management
|
||||
root.then(NPCCommand.createNPCCommand());
|
||||
// /tiedup key ... - Key management
|
||||
root.then(KeyCommand.createKeyCommand());
|
||||
// /tiedup collar ... - Collar management
|
||||
root.then(CollarCommand.createCollarCommand());
|
||||
// /tiedup clothes ... - Clothes configuration
|
||||
root.then(ClothesCommand.createClothesCommand());
|
||||
// /tiedup kidnapset - Configuration reload
|
||||
root.then(KidnapSetCommand.createKidnapSetCommand());
|
||||
// /tiedup kidnapreload - Configuration reload (alias)
|
||||
root.then(KidnapSetCommand.createKidnapReloadCommand());
|
||||
// /tiedup cell ... - Cell management
|
||||
root.then(CellCommand.createCellCommand());
|
||||
// === Social Commands ===
|
||||
root.then(SocialCommand.createBlockPlayerCommand());
|
||||
root.then(SocialCommand.createUnblockPlayerCommand());
|
||||
root.then(SocialCommand.createCheckBlockedCommand());
|
||||
root.then(SocialCommand.createNoRPCommand());
|
||||
root.then(SocialCommand.createMeCommand());
|
||||
root.then(SocialCommand.createPMCommand());
|
||||
root.then(SocialCommand.createTalkAreaCommand());
|
||||
root.then(SocialCommand.createTalkInfoCommand());
|
||||
// /tiedup debug ... - Captivity system debugging
|
||||
root.then(CaptivityDebugCommand.createDebugCommand());
|
||||
|
||||
dispatcher.register(root);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,223 @@
|
||||
package com.tiedup.remake.commands.subcommands;
|
||||
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
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.prison.PrisonerManager;
|
||||
import com.tiedup.remake.prison.RansomRecord;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Debt management sub-commands for /tiedup.
|
||||
* Handles: debt show, debt set, debt add, debt remove
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public class DebtSubCommand {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
// /tiedup debt <player> [set|add|remove <amount>]
|
||||
root.then(
|
||||
Commands.literal("debt")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
// /tiedup debt <player> -> show debt
|
||||
.executes(DebtSubCommand::debtShow)
|
||||
// /tiedup debt <player> set <amount>
|
||||
.then(
|
||||
Commands.literal("set").then(
|
||||
Commands.argument(
|
||||
"amount",
|
||||
IntegerArgumentType.integer(0)
|
||||
).executes(DebtSubCommand::debtSet)
|
||||
)
|
||||
)
|
||||
// /tiedup debt <player> add <amount>
|
||||
.then(
|
||||
Commands.literal("add").then(
|
||||
Commands.argument(
|
||||
"amount",
|
||||
IntegerArgumentType.integer(0)
|
||||
).executes(DebtSubCommand::debtAdd)
|
||||
)
|
||||
)
|
||||
// /tiedup debt <player> remove <amount>
|
||||
.then(
|
||||
Commands.literal("remove").then(
|
||||
Commands.argument(
|
||||
"amount",
|
||||
IntegerArgumentType.integer(0)
|
||||
).executes(DebtSubCommand::debtRemove)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Command Implementations
|
||||
// ========================================
|
||||
|
||||
private static int debtShow(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
RansomRecord ransom = manager.getRansomRecord(target.getUUID());
|
||||
|
||||
if (ransom == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
target.getName().getString() +
|
||||
" has no debt record."
|
||||
),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int total = ransom.getTotalDebt();
|
||||
int paid = ransom.getAmountPaid();
|
||||
int remaining = ransom.getRemainingDebt();
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
target.getName().getString() +
|
||||
" \u2014 Debt: " +
|
||||
total +
|
||||
" | Paid: " +
|
||||
paid +
|
||||
" | Remaining: " +
|
||||
remaining +
|
||||
" emeralds"
|
||||
),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int debtSet(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
int amount = IntegerArgumentType.getInteger(context, "amount");
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
RansomRecord ransom = manager.getRansomRecord(target.getUUID());
|
||||
|
||||
if (ransom == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " has no debt record."
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ransom.setTotalDebt(amount);
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Set " +
|
||||
target.getName().getString() +
|
||||
"'s total debt to " +
|
||||
amount +
|
||||
" emeralds."
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int debtAdd(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
int amount = IntegerArgumentType.getInteger(context, "amount");
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
RansomRecord ransom = manager.getRansomRecord(target.getUUID());
|
||||
|
||||
if (ransom == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " has no debt record."
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ransom.increaseDebt(amount);
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Added " +
|
||||
amount +
|
||||
" emeralds to " +
|
||||
target.getName().getString() +
|
||||
"'s debt. Remaining: " +
|
||||
ransom.getRemainingDebt()
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int debtRemove(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "player");
|
||||
int amount = IntegerArgumentType.getInteger(context, "amount");
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
RansomRecord ransom = manager.getRansomRecord(target.getUUID());
|
||||
|
||||
if (ransom == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
target.getName().getString() + " has no debt record."
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ransom.addPayment(amount, null);
|
||||
boolean paid = ransom.isPaid();
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Removed " +
|
||||
amount +
|
||||
" emeralds from " +
|
||||
target.getName().getString() +
|
||||
"'s debt. Remaining: " +
|
||||
ransom.getRemainingDebt() +
|
||||
(paid ? " (PAID OFF!)" : "")
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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.cells.ConfiscatedInventoryRegistry;
|
||||
import com.tiedup.remake.commands.CommandHelper;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Inventory management sub-commands for /tiedup.
|
||||
* Handles: returnstuff
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public class InventorySubCommand {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
// /tiedup returnstuff <player>
|
||||
root.then(
|
||||
Commands.literal("returnstuff")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(InventorySubCommand::returnstuff)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Command Implementations
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* /tiedup returnstuff <player>
|
||||
*
|
||||
* Restore a player's confiscated inventory from the NBT backup.
|
||||
* Items are given directly to the player, bypassing the chest.
|
||||
*/
|
||||
private static int returnstuff(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "player");
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
|
||||
ConfiscatedInventoryRegistry registry =
|
||||
ConfiscatedInventoryRegistry.get(level);
|
||||
|
||||
if (!registry.hasConfiscatedInventory(targetPlayer.getUUID())) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
targetPlayer.getName().getString() +
|
||||
" has no confiscated inventory to restore"
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean restored = registry.restoreInventory(targetPlayer);
|
||||
|
||||
if (restored) {
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"\u00a7aRestored confiscated inventory to " +
|
||||
targetPlayer.getName().getString()
|
||||
),
|
||||
true
|
||||
);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
targetPlayer,
|
||||
SystemMessageManager.MessageCategory.INFO,
|
||||
"Your confiscated items have been returned!"
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal(
|
||||
"Failed to restore inventory for " +
|
||||
targetPlayer.getName().getString()
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package com.tiedup.remake.commands.subcommands;
|
||||
|
||||
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.entities.EntityMaster;
|
||||
import com.tiedup.remake.entities.ModEntities;
|
||||
import com.tiedup.remake.entities.ai.master.MasterState;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.MobSpawnType;
|
||||
|
||||
/**
|
||||
* Master/pet-play test sub-commands for /tiedup.
|
||||
* Handles: mastertest, masterchair, mastertask
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public class MasterTestSubCommand {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
// /tiedup mastertest
|
||||
root.then(
|
||||
Commands.literal("mastertest")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.executes(MasterTestSubCommand::mastertest)
|
||||
);
|
||||
// /tiedup masterchair
|
||||
root.then(
|
||||
Commands.literal("masterchair")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.executes(MasterTestSubCommand::masterchair)
|
||||
);
|
||||
// /tiedup mastertask <task>
|
||||
root.then(
|
||||
Commands.literal("mastertask")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("task", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> {
|
||||
for (MasterState s : MasterState.values()) {
|
||||
builder.suggest(s.name().toLowerCase());
|
||||
}
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.executes(MasterTestSubCommand::mastertask)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Command Implementations
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* /tiedup mastertest
|
||||
*
|
||||
* Spawn a Master NPC nearby and immediately become its pet.
|
||||
* For testing the pet play system without going through capture.
|
||||
*/
|
||||
private static int mastertest(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer player = context.getSource().getPlayerOrException();
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
|
||||
double x = player.getX() + player.getLookAngle().x * 2;
|
||||
double y = player.getY();
|
||||
double z = player.getZ() + player.getLookAngle().z * 2;
|
||||
|
||||
EntityMaster master = ModEntities.MASTER.get().create(level);
|
||||
if (master == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal("Failed to create Master entity")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
master.moveTo(x, y, z, player.getYRot() + 180F, 0.0F);
|
||||
master.finalizeSpawn(
|
||||
level,
|
||||
level.getCurrentDifficultyAt(master.blockPosition()),
|
||||
MobSpawnType.COMMAND,
|
||||
null,
|
||||
null
|
||||
);
|
||||
level.addFreshEntity(master);
|
||||
|
||||
master.setPetPlayer(player);
|
||||
master.putPetCollar(player);
|
||||
|
||||
CommandHelper.syncPlayerState(player, PlayerBindState.getInstance(player));
|
||||
|
||||
String masterName = master.getNpcName();
|
||||
if (masterName == null || masterName.isEmpty()) masterName = "Master";
|
||||
|
||||
String finalName = masterName;
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Spawned Master '" +
|
||||
finalName +
|
||||
"' \u2014 you are now their pet."
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup masterchair
|
||||
*
|
||||
* Force the nearest Master NPC into HUMAN_CHAIR state.
|
||||
* Requires the player to be that Master's pet.
|
||||
*/
|
||||
private static int masterchair(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer player = context.getSource().getPlayerOrException();
|
||||
EntityMaster master = findNearestMaster(player);
|
||||
|
||||
if (master == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal("No Master NPC found within 20 blocks")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!master.hasPet()) {
|
||||
master.setPetPlayer(player);
|
||||
master.putPetCollar(player);
|
||||
CommandHelper.syncPlayerState(player, PlayerBindState.getInstance(player));
|
||||
}
|
||||
|
||||
master.setMasterState(MasterState.HUMAN_CHAIR);
|
||||
|
||||
String masterName = master.getNpcName();
|
||||
if (masterName == null || masterName.isEmpty()) masterName = "Master";
|
||||
String finalName = masterName;
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Forced " + finalName + " into HUMAN_CHAIR state"
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup mastertask <state>
|
||||
*
|
||||
* Force the nearest Master NPC into any MasterState.
|
||||
* Useful for testing specific master behaviors.
|
||||
*/
|
||||
private static int mastertask(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
ServerPlayer player = context.getSource().getPlayerOrException();
|
||||
String taskName = StringArgumentType.getString(context, "task");
|
||||
|
||||
MasterState targetState;
|
||||
try {
|
||||
targetState = MasterState.valueOf(taskName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal("Unknown MasterState: " + taskName)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EntityMaster master = findNearestMaster(player);
|
||||
if (master == null) {
|
||||
context
|
||||
.getSource()
|
||||
.sendFailure(
|
||||
Component.literal("No Master NPC found within 20 blocks")
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!master.hasPet()) {
|
||||
master.setPetPlayer(player);
|
||||
master.putPetCollar(player);
|
||||
CommandHelper.syncPlayerState(player, PlayerBindState.getInstance(player));
|
||||
}
|
||||
|
||||
master.setMasterState(targetState);
|
||||
|
||||
String masterName = master.getNpcName();
|
||||
if (masterName == null || masterName.isEmpty()) masterName = "Master";
|
||||
String finalName = masterName;
|
||||
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Forced " +
|
||||
finalName +
|
||||
" into " +
|
||||
targetState.name() +
|
||||
" state"
|
||||
),
|
||||
true
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest EntityMaster within 20 blocks of a player.
|
||||
*/
|
||||
@javax.annotation.Nullable
|
||||
private static EntityMaster findNearestMaster(ServerPlayer player) {
|
||||
var masters = player
|
||||
.level()
|
||||
.getEntitiesOfClass(
|
||||
EntityMaster.class,
|
||||
player.getBoundingBox().inflate(20.0),
|
||||
m -> m.isAlive()
|
||||
);
|
||||
if (masters.isEmpty()) return null;
|
||||
return masters
|
||||
.stream()
|
||||
.min((a, b) ->
|
||||
Double.compare(a.distanceToSqr(player), b.distanceToSqr(player))
|
||||
)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.tiedup.remake.commands.subcommands;
|
||||
|
||||
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.network.ModNetwork;
|
||||
import com.tiedup.remake.network.sync.PacketPlayTestAnimation;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Test animation sub-commands for /tiedup.
|
||||
* Handles: testanim <animId> [player], testanim stop [player]
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public class TestAnimSubCommand {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
// /tiedup testanim <animId> [player]
|
||||
// /tiedup testanim stop [player]
|
||||
root.then(
|
||||
Commands.literal("testanim")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.literal("stop")
|
||||
.executes(ctx -> testAnimStop(ctx, null))
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(ctx ->
|
||||
testAnimStop(
|
||||
ctx,
|
||||
EntityArgument.getPlayer(ctx, "player")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(
|
||||
Commands.argument("animId", StringArgumentType.string())
|
||||
.executes(ctx -> testAnim(ctx, null))
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(ctx ->
|
||||
testAnim(
|
||||
ctx,
|
||||
EntityArgument.getPlayer(ctx, "player")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Command Implementations
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* /tiedup testanim <animId> [player]
|
||||
* Play an animation from player_animation/ on a player.
|
||||
*/
|
||||
private static int testAnim(
|
||||
CommandContext<CommandSourceStack> context,
|
||||
ServerPlayer target
|
||||
) throws CommandSyntaxException {
|
||||
if (target == null) {
|
||||
target = context.getSource().getPlayerOrException();
|
||||
}
|
||||
String animId = StringArgumentType.getString(context, "animId");
|
||||
|
||||
ModNetwork.sendToAllTrackingAndSelf(
|
||||
new PacketPlayTestAnimation(target.getUUID(), animId),
|
||||
target
|
||||
);
|
||||
|
||||
final String name = target.getName().getString();
|
||||
final String anim = animId;
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() ->
|
||||
Component.literal(
|
||||
"Playing animation '" + anim + "' on " + name
|
||||
),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* /tiedup testanim stop [player]
|
||||
* Stop animation on a player.
|
||||
*/
|
||||
private static int testAnimStop(
|
||||
CommandContext<CommandSourceStack> context,
|
||||
ServerPlayer target
|
||||
) throws CommandSyntaxException {
|
||||
if (target == null) {
|
||||
target = context.getSource().getPlayerOrException();
|
||||
}
|
||||
|
||||
ModNetwork.sendToAllTrackingAndSelf(
|
||||
new PacketPlayTestAnimation(target.getUUID(), ""),
|
||||
target
|
||||
);
|
||||
|
||||
final String name = target.getName().getString();
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
() -> Component.literal("Stopped animation on " + name),
|
||||
false
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user