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:
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user