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

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
);
}
}
}