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 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 find(UUID id) { return Optional.ofNullable(identities.get(id)); } public List 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 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; } }