Volver al índice

src/main/java/ar/com/companeros/Commands.java

package ar.com.companeros;

import ar.com.companeros.runtime.AgentRuntime;
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 net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.event.RegisterCommandsEvent;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;

/** Comandos de jugadores autónomos y compatibilidad con los compañeros del prototipo. */
public final class Commands {
    private Commands() { }

    public static void register(RegisterCommandsEvent event) {
        var root = literal("raps").requires(source ->
            !(source.getEntity() instanceof net.minecraftforge.common.util.FakePlayer)
                && !(source.getEntity() instanceof net.minecraft.server.level.ServerPlayer player
                    && ar.com.companeros.identity.AgentRegistry.get(source.getServer()).find(player.getUUID()).isPresent()));
        root.executes(context -> {
            context.getSource().sendSuccess(() -> Component.literal(
                "Unir uno: /Raps join bot <nombre> · Unir todos: /Raps join bots · Ver bots: /Raps estado · Ayuda: /Raps ayuda"), false);
            return 1;
        });

        // Jugadores registrados: el runtime comprueba identidad, permisos y población.
        root.then(literal("crear").requires(source -> source.hasPermission(2))
            .then(argument("nombre", StringArgumentType.word())
                .executes(context -> AgentRuntime.create(context.getSource(), name(context)))));
        root.then(literal("conectar").then(argument("nombre", StringArgumentType.word())
            .executes(context -> AgentRuntime.connect(context.getSource(), name(context)))));
        root.then(literal("join").requires(source -> source.hasPermission(2))
            .then(literal("bot").then(argument("nombre", StringArgumentType.word())
                .suggests((context, builder) -> net.minecraft.commands.SharedSuggestionProvider.suggest(
                    AgentRuntime.joinableNames(context.getSource().getServer()), builder))
                .executes(context -> AgentRuntime.connect(context.getSource(), name(context)))))
            .then(literal("bots").executes(context -> AgentRuntime.connectAll(context.getSource()))));
        root.then(literal("ayuda").executes(context -> {
            for (String line : new String[]{
                    "/Raps join bot Neon — conecta un bot guardado",
                    "/Raps join bots — conecta todos los bots disponibles",
                    "/Raps crear Nombre — crea un bot nuevo",
                    "/Raps rol Neon investigador · /Raps rol Xenon generalista",
                    "/Raps skin Neon CuentaMinecraft · /Raps skin Neon default",
                    "/Raps autonomo Nombre — permite decidir a la IA cuando esté habilitada",
                    "/Raps estado · /Raps inventario Nombre",
                    "/Raps pausar Nombre — guarda y desconecta",
                    "/Raps tools estado Nombre · /Raps tools proponer Nombre JSON",
                    "/Raps herramienta Nombre create_banner JSON · place_banner JSON",
                    "/Raps mimicer estado · meteorito · infectar Nombre · etapa 0..4 · avanzar · spawn",
                    "/sensitivity mode enabled · /sensitivity mode disabled",
                    "/botmalo Nombre mensaje · /botmalo all mensaje"})
                context.getSource().sendSuccess(() -> Component.literal(line), false);
            return 1;
        }));
        root.then(literal("pausar").then(argument("nombre", StringArgumentType.word())
            .executes(context -> registered(context) ? AgentRuntime.pause(context.getSource(), name(context))
                : changeLegacyMode(context, "quieto"))));

        // El cuerpo inicial sigue disponible para comparar y mantener mundos anteriores.
        root.then(literal("crear_prototipo").requires(source -> source.hasPermission(2))
            .then(argument("nombre", StringArgumentType.word())
                .executes(context -> createPrototype(context, 24))
                .then(argument("radio", IntegerArgumentType.integer(8, 32))
                    .executes(context -> createPrototype(context, IntegerArgumentType.getInteger(context, "radio"))))));
        root.then(literal("campamento").then(argument("nombre", StringArgumentType.word()).executes(Commands::camp)));
        root.then(literal("estado").executes(Commands::list));
        root.then(literal("inventario").then(argument("nombre", StringArgumentType.word()).executes(Commands::inventory)));
        root.then(literal("entregar").then(argument("nombre", StringArgumentType.word()).executes(Commands::handOver)));
        for (String mode : new String[]{"autonomo", "seguir", "quieto"}) {
            root.then(literal(mode).then(argument("nombre", StringArgumentType.word()).executes(context -> {
                if (registered(context)) return AgentRuntime.setMode(context.getSource(), name(context), mode);
                return changeLegacyMode(context, mode);
            })));
        }
        root.then(literal("retirar").requires(source -> source.hasPermission(2))
            .then(argument("nombre", StringArgumentType.word()).executes(Commands::retire)));

        // Herramientas supervisadas y escalones de población permitidos para las pruebas.
        root.then(literal("herramientas").executes(context -> {
            var tools = AgentRuntime.toolNames(context.getSource().getServer());
            for (String tool : tools) context.getSource().sendSuccess(() -> Component.literal(tool), false);
            if (tools.isEmpty()) context.getSource().sendSuccess(
                () -> Component.literal("Todavía no hay herramientas disponibles."), false);
            return tools.size();
        }));
        root.then(literal("herramienta").requires(source -> source.hasPermission(2))
            .then(argument("nombre", StringArgumentType.word())
                .then(argument("herramienta", StringArgumentType.word())
                    .executes(context -> runTool(context, "{}"))
                    .then(argument("argumentos", StringArgumentType.greedyString())
                        .executes(context -> runTool(context, StringArgumentType.getString(context, "argumentos")))))));
        var population = literal("fase").requires(source -> source.hasPermission(2));
        for (int limit : new int[]{2, 4, 6, 10})
            population.then(literal(Integer.toString(limit)).executes(
                context -> AgentRuntime.setPopulationLimit(context.getSource(), limit)));
        root.then(population);
        // Controles de aprendizaje: propuestas con autor real, nunca aprobación por chat.
        root.then(literal("tools").requires(source -> source.hasPermission(2))
            .then(literal("estado").then(argument("nombre", StringArgumentType.word())
                .executes(context -> AgentRuntime.toolState(context.getSource(), name(context)))))
            .then(literal("proponer").then(argument("nombre", StringArgumentType.word())
                .then(argument("manifiesto", StringArgumentType.greedyString()).executes(context ->
                    AgentRuntime.proposeTool(context.getSource(), name(context), StringArgumentType.getString(context, "manifiesto")))))));
        // Especialización guardada; puede asignarse antes de conectar al jugador.
        root.then(literal("rol").requires(source -> source.hasPermission(2))
            .then(argument("nombre", StringArgumentType.word())
                .then(literal("investigador").executes(c -> AgentRuntime.setRole(c.getSource(), name(c), ar.com.companeros.identity.AgentRoles.Role.INVESTIGADOR)))
                .then(literal("generalista").executes(c -> AgentRuntime.setRole(c.getSource(), name(c), ar.com.companeros.identity.AgentRoles.Role.GENERALISTA)))));
        ar.com.companeros.horror.HorrorModule.registerCommands(root);
        root.then(literal("skin").requires(source -> source.hasPermission(2))
            .then(argument("nombre", StringArgumentType.word())
                .executes(c -> ar.com.companeros.identity.SkinProfiles.command(c.getSource(), name(c), null))
                .then(argument("cuenta", StringArgumentType.word()).executes(c -> ar.com.companeros.identity.SkinProfiles.command(
                    c.getSource(), name(c), StringArgumentType.getString(c, "cuenta"))))));
        ar.com.companeros.horror.HorrorModule.registerSensitivity(event.getDispatcher());
        var registered = event.getDispatcher().register(root);
        event.getDispatcher().register(literal("Raps").requires(registered.getRequirement()).executes(registered.getCommand()).redirect(registered));
        event.getDispatcher().register(literal("companeros").requires(registered.getRequirement()).executes(registered.getCommand()).redirect(registered));
    }

    private static String name(CommandContext<CommandSourceStack> context) {
        return StringArgumentType.getString(context, "nombre");
    }

    private static boolean registered(CommandContext<CommandSourceStack> context) {
        return AgentRuntime.isRegisteredPlayer(context.getSource().getServer(), name(context));
    }

    private static int runTool(CommandContext<CommandSourceStack> context, String json) throws CommandSyntaxException {
        return AgentRuntime.runTool(context.getSource(), name(context),
            StringArgumentType.getString(context, "herramienta"), json);
    }

    // Adaptación de controles: primero jugadores reales; después el inventario/cuerpo legado.
    private static int changeLegacyMode(CommandContext<CommandSourceStack> context, String mode) throws CommandSyntaxException {
        Companion bot = owned(context);
        if (bot == null) return 0;
        bot.mode = mode; bot.resource = null; bot.miningProgress = 0;
        bot.getNavigation().stop(); bot.setTarget(null);
        context.getSource().sendSuccess(() -> Component.literal(bot.getName().getString() + ": " + mode), false);
        return 1;
    }

    private static int handOver(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
        if (registered(context)) return AgentRuntime.handOver(context.getSource(), name(context));
        Companion bot = owned(context);
        if (bot == null) return 0;
        var player = context.getSource().getPlayerOrException();
        if (bot.distanceToSqr(player) > 16) {
            context.getSource().sendFailure(Component.literal("Acercate al compañero para recibir sus objetos.")); return 0;
        }
        for (int i = 0; i < bot.bag.getContainerSize(); i++) {
            var stack = bot.bag.removeItemNoUpdate(i);
            if (!stack.isEmpty()) {
                var drop = bot.spawnAtLocation(stack);
                if (drop != null) { drop.setTarget(player.getUUID()); drop.setThrower(player.getUUID()); }
            }
        }
        bot.setItemSlot(net.minecraft.world.entity.EquipmentSlot.MAINHAND, net.minecraft.world.item.ItemStack.EMPTY);
        context.getSource().sendSuccess(() -> Component.literal("Objetos entregados."), false);
        return 1;
    }

    private static int retire(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
        if (registered(context)) return AgentRuntime.retire(context.getSource(), name(context));
        Companion bot = owned(context);
        if (bot == null) return 0;
        bot.releaseInventory(); WorkArea.get(bot.serverLevel()).forget(bot.getUUID());
        var registry = ar.com.companeros.identity.AgentRegistry.get(bot.serverLevel().getServer());
        registry.find(bot.getUUID()).ifPresent(identity -> registry.markState(identity.id(), ar.com.companeros.identity.AgentState.RETIRED));
        bot.discard();
        context.getSource().sendSuccess(() -> Component.literal("Compañero retirado."), false);
        return 1;
    }

    private static int createPrototype(CommandContext<CommandSourceStack> context, int radius) throws CommandSyntaxException {
        var player = context.getSource().getPlayerOrException();
        var level = player.serverLevel();
        String name = StringArgumentType.getString(context, "nombre");
        if (!name.matches("[A-Za-z0-9_]{1,16}")) {
            context.getSource().sendFailure(Component.literal("Usá un nombre de hasta 16 letras, números o guiones bajos."));
            return 0;
        }
        long existing = WorkArea.get(level).companionCount();
        if (AgentRuntime.isRegisteredPlayer(context.getSource().getServer(), name)
                || existing >= 4 || level.getEntities(Companeros.COMPANION.get(), bot -> bot.getName().getString().equals(name)).size() > 0) {
            context.getSource().sendFailure(Component.literal("Ya existe ese nombre o hay cuatro compañeros en esta dimensión."));
            return 0;
        }
        Companion bot = Companeros.COMPANION.get().create(level);
        if (bot == null) return 0;
        bot.moveTo(player.getX() + 1.5, player.getY(), player.getZ(), player.getYRot(), 0);
        if (!level.noCollision(bot) || !level.getFluidState(bot.blockPosition()).isEmpty()) {
            context.getSource().sendFailure(Component.literal("Necesitás espacio libre a tu lado.")); return 0;
        }
        bot.owner = player.getUUID(); bot.home = bot.blockPosition(); bot.radius = radius;
        bot.mode = "quieto"; bot.activity = "Esperando campamento";
        bot.setCustomName(Component.literal(name)); bot.setCustomNameVisible(true);
        if (!level.addFreshEntity(bot)) return 0;
        context.getSource().sendSuccess(() -> Component.literal(name + " se sumó a la aventura."), false);
        return 1;
    }

    private static Companion owned(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
        var player = context.getSource().getPlayerOrException();
        String name = StringArgumentType.getString(context, "nombre");
        for (Companion bot : player.serverLevel().getEntitiesOfClass(Companion.class,
                player.getBoundingBox().inflate(96), entity -> entity.getName().getString().equals(name))) {
            if (player.getUUID().equals(bot.owner) || context.getSource().hasPermission(2)) return bot;
        }
        context.getSource().sendFailure(Component.literal("No encontré un compañero tuyo con ese nombre cerca."));
        return null;
    }

    private static int camp(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
        if (registered(context)) return AgentRuntime.setCamp(context.getSource(), name(context));
        Companion bot = owned(context);
        if (bot == null) return 0;
        var home = context.getSource().getPlayerOrException().blockPosition();
        if (!bot.serverLevel().getEntitiesOfClass(Companion.class, new AABB(home).inflate(8),
                other -> other != bot && other.building && other.home != null && other.home.distSqr(home) < 64).isEmpty()) {
            context.getSource().sendFailure(Component.literal("Separá los campamentos por al menos ocho bloques.")); return 0;
        }
        if (!Shelter.clearSite(bot, home)) {
            context.getSource().sendFailure(Component.literal("Elegí un terreno plano con 5×5 bloques libres y cuatro de altura."));
            return 0;
        }
        bot.home = home.immutable(); bot.building = true; bot.shelterStep = 0;
        bot.resource = null; bot.searchCursor = 0; bot.blocked.clear(); bot.mode = "autonomo";
        context.getSource().sendSuccess(() -> Component.literal("Campamento de " + bot.getName().getString() + " marcado."), false);
        return 1;
    }

    private static int list(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
        var server = context.getSource().getServer();
        var descriptions = AgentRuntime.describe(server);
        int total = descriptions.size();
        for (String description : descriptions)
            context.getSource().sendSuccess(() -> Component.literal(description), false);
        var seen = new java.util.HashSet<java.util.UUID>();
        for (var level : server.getAllLevels()) {
            var bots = level.getEntities(Companeros.COMPANION.get(), bot ->
                !AgentRuntime.isRegisteredPlayer(server, bot.getName().getString()));
            for (Companion bot : bots) {
                if (!seen.add(bot.getUUID())) continue;
                total++;
                context.getSource().sendSuccess(() -> Component.literal(
                    bot.getName().getString() + " · " + bot.activity + " · Salud " + (int) bot.getHealth()
                    + "/20 · Comida " + bot.hunger + "/20 · " + bot.blockPosition().toShortString()), false);
            }
        }
        if (total == 0) context.getSource().sendSuccess(() -> Component.literal("No hay compañeros."), false);
        return total;
    }

    private static int inventory(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
        if (registered(context)) return AgentRuntime.inventory(context.getSource(), name(context));
        Companion bot = owned(context);
        if (bot == null) return 0;
        java.util.List<String> items = new java.util.ArrayList<>();
        for (int i = 0; i < bot.bag.getContainerSize(); i++) {
            var stack = bot.bag.getItem(i);
            if (!stack.isEmpty()) items.add(stack.getCount() + " " + stack.getHoverName().getString());
        }
        context.getSource().sendSuccess(() -> Component.literal(bot.getName().getString() + ": "
            + (items.isEmpty() ? "Inventario vacío" : String.join(", ", items))), false);
        return 1;
    }
}