Volver al índice

src/main/java/ar/com/companeros/identity/AgentRegistry.java

package ar.com.companeros.identity;

import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.GlobalPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.saveddata.SavedData;
import net.minecraftforge.common.util.FakePlayer;

/** Registro global guardado en el overworld, sin depender de chunks cargados. */
public final class AgentRegistry extends SavedData {
    // Identidad global y formato versionado.
    public static final int SCHEMA_VERSION = 1;
    private static final int MAX_IDENTITIES = 256;
    private final UUID worldId;
    private final Map<UUID, AgentIdentity> identities = new LinkedHashMap<>();

    public AgentRegistry() { this(UUID.randomUUID()); setDirty(); }
    private AgentRegistry(UUID worldId) { this.worldId = worldId; }

    public static AgentRegistry get(MinecraftServer server) {
        if (!server.isSameThread())
            throw new IllegalStateException("El registro se usa en el hilo del servidor");
        return server.overworld().getDataStorage().computeIfAbsent(
            AgentRegistry::load, AgentRegistry::new, "companeros_agentes");
    }

    public UUID worldId() { return worldId; }
    public boolean isAgent(UUID id) { return identities.containsKey(id); }
    public boolean isHuman(Player player) {
        return !(player instanceof FakePlayer) && !isAgent(player.getUUID());
    }
    public Optional<AgentIdentity> find(UUID id) { return Optional.ofNullable(identities.get(id)); }
    public List<AgentIdentity> entries() {
        return identities.values().stream().sorted(Comparator.comparing(AgentIdentity::name)
            .thenComparing(identity -> identity.id().toString())).toList();
    }

    // Cambios explícitos: registrar de nuevo no reinicia estado ni fecha de creación.
    public AgentIdentity register(UUID id, String name, UUID legacyOwner, GlobalPos camp, String mode) {
        AgentIdentity previous = identities.get(id);
        if (previous == null && identities.size() >= MAX_IDENTITIES)
            throw new IllegalStateException("Se alcanzó el límite del registro de agentes");
        if (identities.values().stream().anyMatch(other -> !other.id().equals(id)
                && other.state() != AgentState.RETIRED && other.name().equalsIgnoreCase(name)))
            throw new IllegalArgumentException("Ya existe un agente con ese nombre");
        long now = System.currentTimeMillis();
        AgentIdentity next = new AgentIdentity(id, name,
            previous == null ? AgentState.LEGACY : previous.state(), legacyOwner, camp, mode,
            previous == null ? now : previous.createdAtUtc(),
            previous == null ? now : Math.max(now, previous.updatedAtUtc()));
        identities.put(id, next); setDirty();
        return next;
    }

    public AgentIdentity updateSettings(UUID id, UUID legacyOwner, GlobalPos camp, String mode) {
        AgentIdentity current = require(id);
        return register(id, current.name(), legacyOwner, camp, mode);
    }

    public AgentIdentity updateSettings(UUID id, GlobalPos camp, String mode) {
        return updateSettings(id, require(id).legacyOwner(), camp, mode);
    }

    public AgentIdentity markState(UUID id, AgentState state) {
        AgentIdentity current = require(id);
        AgentIdentity next = new AgentIdentity(current.id(), current.name(), state,
            current.legacyOwner(), current.camp(), current.mode(), current.createdAtUtc(),
            Math.max(System.currentTimeMillis(), current.updatedAtUtc()));
        identities.put(id, next); setDirty();
        return next;
    }

    private AgentIdentity require(UUID id) {
        AgentIdentity identity = identities.get(id);
        if (identity == null) throw new IllegalArgumentException("Agente no registrado");
        return identity;
    }

    // Guardado y carga: un formato futuro o identidad inválida nunca borra datos en silencio.
    @Override public CompoundTag save(CompoundTag tag) {
        tag.putInt("SchemaVersion", SCHEMA_VERSION);
        tag.putUUID("WorldId", worldId);
        ListTag list = new ListTag();
        for (AgentIdentity identity : entries()) {
            CompoundTag entry = new CompoundTag();
            entry.putUUID("Id", identity.id()); entry.putString("Name", identity.name());
            entry.putString("State", identity.state().name()); entry.putString("Mode", identity.mode());
            entry.putLong("CreatedAtUtc", identity.createdAtUtc());
            entry.putLong("UpdatedAtUtc", identity.updatedAtUtc());
            if (identity.legacyOwner() != null) entry.putUUID("LegacyOwner", identity.legacyOwner());
            if (identity.camp() != null) {
                entry.putString("Dimension", identity.camp().dimension().location().toString());
                entry.putLong("Camp", identity.camp().pos().asLong());
            }
            list.add(entry);
        }
        tag.put("Agents", list);
        return tag;
    }

    public static AgentRegistry load(CompoundTag tag) {
        if (tag.getInt("SchemaVersion") > SCHEMA_VERSION)
            throw new IllegalStateException("Formato de agentes más nuevo que el mod");
        if (tag.getInt("SchemaVersion") == SCHEMA_VERSION
                && (!tag.hasUUID("WorldId") || !tag.contains("Agents", 9)))
            throw new IllegalStateException("Registro de agentes incompleto");
        AgentRegistry data = new AgentRegistry(tag.hasUUID("WorldId") ? tag.getUUID("WorldId") : UUID.randomUUID());
        ListTag list = tag.getList("Agents", 10);
        if (list.size() > MAX_IDENTITIES)
            throw new IllegalStateException("Registro de agentes fuera de límite");
        java.util.Set<String> activeNames = new java.util.HashSet<>();
        for (int i = 0; i < list.size(); i++) {
            CompoundTag entry = list.getCompound(i);
            GlobalPos camp = entry.contains("Camp") ? GlobalPos.of(ResourceKey.create(Registries.DIMENSION,
                new ResourceLocation(entry.getString("Dimension"))), BlockPos.of(entry.getLong("Camp"))) : null;
            UUID id = entry.getUUID("Id");
            AgentIdentity identity = new AgentIdentity(id, entry.getString("Name"),
                AgentState.valueOf(entry.getString("State")),
                entry.hasUUID("LegacyOwner") ? entry.getUUID("LegacyOwner") : null,
                camp, entry.getString("Mode"), entry.getLong("CreatedAtUtc"), entry.getLong("UpdatedAtUtc"));
            if (identity.state() != AgentState.RETIRED
                    && !activeNames.add(identity.name().toLowerCase(java.util.Locale.ROOT)))
                throw new IllegalStateException("Nombre duplicado en registro de agentes");
            if (data.identities.putIfAbsent(id, identity) != null)
                throw new IllegalStateException("UUID duplicado en registro de agentes");
        }
        if (!tag.hasUUID("WorldId")) data.setDirty();
        return data;
    }
}