Phase 3: Migrate Component.literal() in all remaining command files. - NPCCommand (34), CellCommand (33), SocialCommand (16), CollarCommand (25), KeyCommand (18), BountyCommand (6), KidnapSetCommand (2), CaptivityDebugCommand (7), InventorySubCommand (3), TestAnimSubCommand (2), MasterTestSubCommand (7), DebtSubCommand (8) - Strip all section sign color codes, use .withStyle(ChatFormatting) - 148 new keys in en_us.json (command.tiedup.*) - Debug/dynamic strings intentionally kept as literal
653 lines
22 KiB
Java
653 lines
22 KiB
Java
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.ChatFormatting;
|
|
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.translatable("command.tiedup.error.must_be_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.translatable("command.tiedup.cell.no_selection")
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
CellDataV2 cell = registry.getCell(selectedCellId);
|
|
if (cell == null) {
|
|
source.sendFailure(
|
|
Component.translatable("command.tiedup.cell.no_longer_exists")
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
// Check name uniqueness
|
|
CellDataV2 existingCell = registry.getCellByName(name);
|
|
if (
|
|
existingCell != null && !existingCell.getId().equals(selectedCellId)
|
|
) {
|
|
source.sendFailure(
|
|
Component.translatable("command.tiedup.cell.name_exists", name)
|
|
);
|
|
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.translatable("command.tiedup.cell.named", name).withStyle(ChatFormatting.GREEN),
|
|
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.translatable("command.tiedup.cell.none_registered"),
|
|
false
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
source.sendSuccess(
|
|
() -> Component.translatable("command.tiedup.cell.list_header", cells.size()).withStyle(ChatFormatting.GOLD),
|
|
false
|
|
);
|
|
|
|
for (CellDataV2 cell : cells) {
|
|
String info = formatCellInfo(cell, serverLevel);
|
|
source.sendSuccess(() -> Component.literal(info).withStyle(ChatFormatting.GRAY), 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.translatable("command.tiedup.cell.no_cells_for_owner", owner.getName().getString()),
|
|
false
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
source.sendSuccess(
|
|
() ->
|
|
Component.translatable(
|
|
"command.tiedup.cell.list_owner_header",
|
|
owner.getName().getString(),
|
|
cells.size()
|
|
).withStyle(ChatFormatting.GOLD),
|
|
false
|
|
);
|
|
|
|
for (CellDataV2 cell : cells) {
|
|
String info = formatCellInfo(cell, serverLevel);
|
|
source.sendSuccess(() -> Component.literal(info).withStyle(ChatFormatting.GRAY), 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.translatable("command.tiedup.error.must_be_player"));
|
|
return 0;
|
|
}
|
|
|
|
ServerLevel serverLevel = player.serverLevel();
|
|
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
|
|
|
UUID selectedCellId = getSelectedCellFromWand(player);
|
|
if (selectedCellId == null) {
|
|
source.sendFailure(
|
|
Component.translatable("command.tiedup.cell.no_selection")
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
CellDataV2 cell = registry.getCell(selectedCellId);
|
|
if (cell == null) {
|
|
source.sendFailure(
|
|
Component.translatable("command.tiedup.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.translatable("command.tiedup.cell.not_found", name)
|
|
);
|
|
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.translatable("command.tiedup.error.must_be_player"));
|
|
return 0;
|
|
}
|
|
|
|
ServerLevel serverLevel = player.serverLevel();
|
|
CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
|
|
|
|
UUID selectedCellId = getSelectedCellFromWand(player);
|
|
if (selectedCellId == null) {
|
|
source.sendFailure(
|
|
Component.translatable("command.tiedup.cell.no_selection")
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
CellDataV2 cell = registry.getCell(selectedCellId);
|
|
if (cell == null) {
|
|
source.sendFailure(
|
|
Component.translatable("command.tiedup.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.translatable("command.tiedup.cell.deleted", cellName).withStyle(ChatFormatting.GREEN),
|
|
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.translatable(
|
|
"command.tiedup.cell.reset_spawns", finalResetCount, finalSpawnMarkerCount, radius
|
|
).withStyle(ChatFormatting.GREEN),
|
|
true
|
|
);
|
|
|
|
if (resetCount > 0) {
|
|
source.sendSuccess(
|
|
() ->
|
|
Component.translatable("command.tiedup.cell.reset_spawns_hint").withStyle(ChatFormatting.GRAY),
|
|
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.translatable("command.tiedup.cell.info_header", nameDisplay).withStyle(ChatFormatting.GOLD),
|
|
false
|
|
);
|
|
|
|
source.sendSuccess(
|
|
() -> Component.translatable("command.tiedup.cell.info_id", cell.getId().toString()).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
|
|
source.sendSuccess(
|
|
() -> Component.translatable("command.tiedup.cell.info_state", cell.getState().toString()).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
|
|
source.sendSuccess(
|
|
() ->
|
|
Component.translatable(
|
|
"command.tiedup.cell.info_core_pos", cell.getCorePos().toShortString()
|
|
).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
|
|
// Spawn point (may be null in V2)
|
|
if (cell.getSpawnPoint() != null) {
|
|
source.sendSuccess(
|
|
() ->
|
|
Component.translatable(
|
|
"command.tiedup.cell.info_spawn_point", cell.getSpawnPoint().toShortString()
|
|
).withStyle(ChatFormatting.GRAY),
|
|
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.translatable(
|
|
"command.tiedup.cell.info_owner",
|
|
ownerName,
|
|
cell.getOwnerId().toString().substring(0, 8) + "..."
|
|
).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
} else {
|
|
source.sendSuccess(
|
|
() -> Component.translatable("command.tiedup.cell.info_owner_world").withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
}
|
|
|
|
// Geometry
|
|
source.sendSuccess(
|
|
() ->
|
|
Component.translatable(
|
|
"command.tiedup.cell.info_interior", cell.getInteriorBlocks().size()
|
|
).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
source.sendSuccess(
|
|
() ->
|
|
Component.translatable(
|
|
"command.tiedup.cell.info_walls", cell.getWallBlocks().size()
|
|
).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
|
|
// Breach info
|
|
if (!cell.getBreachedPositions().isEmpty()) {
|
|
source.sendSuccess(
|
|
() ->
|
|
Component.translatable(
|
|
"command.tiedup.cell.info_breaches",
|
|
cell.getBreachedPositions().size(),
|
|
String.format("%.1f", cell.getBreachPercentage() * 100)
|
|
).withStyle(ChatFormatting.RED),
|
|
false
|
|
);
|
|
}
|
|
|
|
// Features
|
|
if (!cell.getBeds().isEmpty()) {
|
|
source.sendSuccess(
|
|
() -> Component.translatable("command.tiedup.cell.info_beds", cell.getBeds().size()).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
}
|
|
if (!cell.getAnchors().isEmpty()) {
|
|
source.sendSuccess(
|
|
() -> Component.translatable("command.tiedup.cell.info_anchors", cell.getAnchors().size()).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
}
|
|
if (!cell.getDoors().isEmpty()) {
|
|
source.sendSuccess(
|
|
() -> Component.translatable("command.tiedup.cell.info_doors", cell.getDoors().size()).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
}
|
|
|
|
// Prisoners
|
|
source.sendSuccess(
|
|
() ->
|
|
Component.translatable(
|
|
"command.tiedup.cell.info_prisoners", cell.getPrisonerCount()
|
|
).withStyle(ChatFormatting.GRAY),
|
|
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).withStyle(ChatFormatting.GRAY),
|
|
false
|
|
);
|
|
}
|
|
}
|
|
}
|