Files
TiedUp-/src/main/java/com/tiedup/remake/commands/SocialCommand.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

488 lines
17 KiB
Java

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.
*
* 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.translatable("command.tiedup.social.cannot_block_self"));
return 0;
}
SocialData data = SocialData.get(player.serverLevel());
if (data.isBlocked(player.getUUID(), target.getUUID())) {
source.sendFailure(
Component.translatable("command.tiedup.social.already_blocked", target.getName().getString())
);
return 0;
}
data.addBlock(player.getUUID(), target.getUUID());
source.sendSuccess(
() ->
Component.translatable("command.tiedup.social.blocked", target.getName().getString()).withStyle(ChatFormatting.GREEN),
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.translatable("command.tiedup.social.not_blocked", target.getName().getString())
);
return 0;
}
data.removeBlock(player.getUUID(), target.getUUID());
source.sendSuccess(
() ->
Component.translatable("command.tiedup.social.unblocked", target.getName().getString()).withStyle(ChatFormatting.GREEN),
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.translatable("command.tiedup.social.has_blocked_you", target.getName().getString()).withStyle(ChatFormatting.RED),
false
);
} else {
source.sendSuccess(
() ->
Component.translatable("command.tiedup.social.has_not_blocked_you", target.getName().getString()).withStyle(ChatFormatting.GREEN),
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.translatable("command.tiedup.social.norp_cooldown", remaining)
);
return 0;
}
// Set cooldown
NORP_COOLDOWNS.put(player.getUUID(), now);
// Broadcast to all players
Component message = Component.literal("")
.append(
Component.translatable("command.tiedup.social.norp_prefix").withStyle(
ChatFormatting.RED,
ChatFormatting.BOLD
)
)
.append(
Component.literal(player.getName().getString()).withStyle(
ChatFormatting.YELLOW
)
)
.append(
Component.translatable(
"command.tiedup.social.norp_announcement"
).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.translatable("command.tiedup.social.pm_blocked")
);
return 0;
}
// Send to target (earplug-aware)
Component toTarget = Component.literal("")
.append(
Component.translatable(
"command.tiedup.social.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.translatable(
"command.tiedup.social.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.translatable("command.tiedup.social.talkarea_disabled").withStyle(ChatFormatting.GREEN),
false
);
} else {
source.sendSuccess(
() ->
Component.translatable("command.tiedup.social.talkarea_set", distance).withStyle(ChatFormatting.GREEN),
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.translatable("command.tiedup.social.talkinfo_disabled").withStyle(ChatFormatting.YELLOW),
false
);
} else {
source.sendSuccess(
() -> Component.translatable("command.tiedup.social.talkinfo_distance", talkArea).withStyle(ChatFormatting.YELLOW),
false
);
}
return 1;
}
}