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
488 lines
17 KiB
Java
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;
|
|
}
|
|
}
|