Volver al índice
src/main/java/ar/com/companeros/social/AdminChannel.java
package ar.com.companeros.social;
import ar.com.companeros.identity.*;
import com.mojang.brigadier.arguments.StringArgumentType;
import java.util.*;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.MinecraftServer;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.ServerChatEvent;
import static net.minecraft.commands.Commands.*;
/** Canal explícito de administración: un mensaje inline o el siguiente mensaje de chat. */
public final class AdminChannel {
private static final Map<Administrator, Selection> armed = new java.util.concurrent.ConcurrentHashMap<>();
private static final UUID CONSOLE = new UUID(0, 0);
private static boolean lifecycleInstalled;
private record Administrator(MinecraftServer server, UUID id) { }
private record Selection(List<UUID> recipients, long expiresAt) {
private Selection { recipients = List.copyOf(recipients); }
}
private AdminChannel() { }
public static void register(RegisterCommandsEvent event) {
installLifecycle();
var root = literal("botmalo").requires(AdminChannel::allowed);
root.then(literal("cancelar").executes(ctx -> {
clearSelection(ctx.getSource());
ctx.getSource().sendSuccess(() -> Component.literal("Canal de bots cancelado."), false); return 1;
}));
root.then(argument("destino", StringArgumentType.word())
.executes(ctx -> {
var source = ctx.getSource();
String target = StringArgumentType.getString(ctx, "destino");
List<AgentIdentity> selected = recipients(source, target);
if (selected.isEmpty()) return missing(source);
ServerPlayer admin = source.getPlayerOrException();
armed.put(new Administrator(source.getServer(), admin.getUUID()),
new Selection(selected.stream().map(AgentIdentity::id).toList(), System.currentTimeMillis() + 120_000));
source.sendSuccess(() -> Component.literal("Tu próximo mensaje de chat será enviado a " + target + "."), false); return 1;
})
.then(argument("mensaje", StringArgumentType.greedyString()).executes(ctx ->
send(ctx.getSource(), StringArgumentType.getString(ctx, "destino"), StringArgumentType.getString(ctx, "mensaje")))));
event.getDispatcher().register(root);
}
// Solo un humano con permisos o la consola puede generar una intervención de administrador.
private static boolean allowed(CommandSourceStack source) {
return source != null && source.getServer().isSameThread() && source.hasPermission(2) && (source.getEntity() == null
|| source.getEntity() instanceof ServerPlayer player
&& !(player instanceof net.minecraftforge.common.util.FakePlayer)
&& !ar.com.companeros.body.PlayerSessions.isAgent(source.getServer(), player.getUUID())
&& !AgentRegistry.get(source.getServer()).isAgent(player.getUUID()));
}
public static void chat(ServerChatEvent event) {
ServerPlayer admin = event.getPlayer();
Selection selection = armed.remove(new Administrator(admin.server, admin.getUUID()));
if (selection == null) return;
// Aunque haya vencido o perdido OP, este texto privado no vuelve al chat global.
event.setCanceled(true);
if (selection.expiresAt() < System.currentTimeMillis()) {
admin.server.execute(() -> admin.sendSystemMessage(Component.literal("El canal de bots venció. Volvé a usar /botmalo."))); return;
}
String text = event.getRawText();
admin.server.execute(() -> {
if (admin.server.getPlayerList().getPlayer(admin.getUUID()) != admin || admin.hasDisconnected()) return;
CommandSourceStack source = admin.createCommandSourceStack();
if (!allowed(source)) {
source.sendFailure(Component.literal("El canal de bots ya no está autorizado.")); return;
}
sendSelected(source, selection.recipients(), text);
});
}
public static int send(CommandSourceStack source, String target, String message) {
if (!allowed(source)) return 0;
if (target == null || target.isBlank() || target.length() > 48 || message == null) return 0;
clearSelection(source);
var agents = recipients(source, target);
if (agents.isEmpty()) return missing(source);
return sendSelected(source, agents.stream().map(AgentIdentity::id).toList(), message);
}
private static int sendSelected(CommandSourceStack source, List<UUID> selected, String message) {
if (!allowed(source)) return 0;
AgentRegistry registry = AgentRegistry.get(source.getServer());
List<AgentIdentity> agents = selected.stream().distinct().map(registry::find).flatMap(Optional::stream)
.filter(agent -> agent.state() != AgentState.RETIRED).toList();
if (agents.isEmpty()) return missing(source);
UUID adminId = source.getEntity() == null ? CONSOLE : source.getEntity().getUUID();
int delivered = 0;
for (AgentIdentity agent : agents) if (AgentInbox.get(source.getServer()).deliver(agent.id(), adminId,
source.getTextName(), AgentInbox.Kind.ADMIN, message)) delivered++;
int count = delivered;
if (count == 0) source.sendFailure(Component.literal("No se pudo entregar: el mensaje está vacío, es demasiado largo o el buzón está lleno."));
else source.sendSuccess(() -> Component.literal("Mensaje del administrador enviado a " + count + " bot(s)."), false);
return count;
}
private static List<AgentIdentity> recipients(CommandSourceStack source, String target) {
return AgentRegistry.get(source.getServer()).entries().stream().filter(agent -> agent.state() != AgentState.RETIRED)
.filter(agent -> target.equalsIgnoreCase("all") || agent.name().equalsIgnoreCase(target)).toList();
}
private static void clearSelection(CommandSourceStack source) {
if (source.getEntity() != null) armed.remove(new Administrator(source.getServer(), source.getEntity().getUUID()));
}
private static synchronized void installLifecycle() {
if (lifecycleInstalled) return;
lifecycleInstalled = true;
net.minecraftforge.common.MinecraftForge.EVENT_BUS.addListener(AdminChannel::loggedOut);
net.minecraftforge.common.MinecraftForge.EVENT_BUS.addListener(AdminChannel::stopping);
}
private static void loggedOut(net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent event) {
if (event.getEntity() instanceof ServerPlayer player) armed.remove(new Administrator(player.server, player.getUUID()));
}
private static void stopping(net.minecraftforge.event.server.ServerStoppingEvent event) {
armed.keySet().removeIf(key -> key.server() == event.getServer());
}
private static int missing(CommandSourceStack source) {
source.sendFailure(Component.literal("No hay bots registrados con ese nombre.")); return 0;
}
}