Volver al índice

src/main/java/ar/com/companeros/horror/HorrorState.java

package ar.com.companeros.horror;

import java.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.*;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.saveddata.SavedData;

/** Un solo acontecimiento, un solo huésped y un depósito sellado de sus pertenencias. */
public final class HorrorState extends SavedData {
    public static final int SCHEMA = 1;
    private long startTime, meteorTime = -1, infectionTime = -1;
    private BlockPos meteor;
    private UUID host, entityId;
    private String hostName = "", entityDimension = "minecraft:overworld";
    private String appearanceOverride = "";
    private BlockPos entityPosition = BlockPos.ZERO;
    private int minimumStage;
    private boolean transferred, entityCreated, dead;
    private CompoundTag quarantine = new CompoundTag();
    private final Set<UUID> sensitive = new HashSet<>();

    public HorrorState(long startTime) { if (startTime < 0) throw new IllegalArgumentException("Tiempo inválido"); this.startTime = startTime; setDirty(); }
    public static HorrorState get(MinecraftServer server) {
        if (!server.isSameThread()) throw new IllegalStateException("El parásito usa el hilo del servidor");
        return server.overworld().getDataStorage().computeIfAbsent(HorrorState::load,
            () -> new HorrorState(server.overworld().getGameTime()), "raps_parasite");
    }
    public boolean meteorDue(long time, HorrorConfig config) { return meteor == null && time >= startTime && time - startTime >= config.meteorDelayTicks(); }
    public boolean infectionDue(long time, HorrorConfig config) { return host == null && meteor != null && time >= meteorTime && time - meteorTime >= config.infectionDelayTicks(); }
    public boolean meteorLanded() { return meteor != null; }
    public Optional<BlockPos> meteor() { return Optional.ofNullable(meteor); }
    public Optional<UUID> host() { return Optional.ofNullable(host); }
    public String hostName() { return hostName; }
    public String appearanceOverride() { return appearanceOverride; }
    public void appearanceOverride(String value) {
        if (!"".equals(value) && !"steve".equals(value)) throw new IllegalArgumentException("Aspecto del parásito inválido");
        if (!appearanceOverride.equals(value)) { appearanceOverride = value; setDirty(); }
    }
    public boolean active() { return host != null; }
    public boolean transferred() { return transferred; }
    public boolean entityCreated() { return entityCreated; }
    public boolean dead() { return dead; }
    public Optional<UUID> entityId() { return Optional.ofNullable(entityId); }
    public String entityDimension() { return entityDimension; }
    public BlockPos entityPosition() { return entityPosition; }
    public long infectionTime() { return infectionTime; }
    public CompoundTag quarantine() { return quarantine.copy(); }
    public boolean sensitive(UUID player) { return sensitive.contains(player); }
    public void sensitivity(UUID player, boolean enabled) { if (enabled ? sensitive.add(player) : sensitive.remove(player)) setDirty(); }

    // El reloj nunca depende del uptime ni de /time set; el avance administrativo sólo eleva el mínimo.
    public int stage(long time, HorrorConfig config) {
        if (!active()) return 0;
        long elapsed = Math.max(0, time - infectionTime);
        return Math.max(minimumStage, (int) Math.min(config.finalStage(), elapsed / config.stageDurationTicks()));
    }
    public void forceStage(int stage) {
        if (!active() || stage < minimumStage || stage > 4) throw new IllegalArgumentException("La mutación sólo avanza entre 0 y 4");
        minimumStage = stage; setDirty();
    }
    public void landed(BlockPos pos, long time) {
        if (meteor != null) throw new IllegalStateException("El meteorito ya cayó");
        meteor = pos.immutable(); meteorTime = time; setDirty();
    }
    public void selectHost(UUID id, String name, long time) {
        if (host != null) { if (host.equals(id)) return; throw new IllegalStateException("Ya existe un huésped"); }
        if (id == null || !name.matches("[A-Za-z0-9_]{1,16}")) throw new IllegalArgumentException("Huésped inválido");
        host = id; hostName = name; infectionTime = time; entityId = UUID.randomUUID(); setDirty();
    }
    public void prepareQuarantine(CompoundTag playerSnapshot, String dimension, BlockPos pos) {
        if (host == null || transferred) throw new IllegalStateException("No se puede iniciar la transferencia");
        if (!quarantine.isEmpty()) return;
        quarantine = playerSnapshot.copy(); entityDimension = dimension; entityPosition = pos.immutable(); setDirty();
    }
    public void commitTransfer() {
        if (quarantine.isEmpty()) throw new IllegalStateException("Falta el respaldo del inventario");
        transferred = true; setDirty();
    }
    public void created() { if (!transferred) throw new IllegalStateException("Inventario pendiente"); entityCreated = true; dead = false; setDirty(); }
    public void location(String dimension, BlockPos pos) {
        if (!entityDimension.equals(dimension) || !entityPosition.equals(pos)) {
            entityDimension = dimension; entityPosition = pos.immutable(); setDirty();
        }
    }
    public void died() { if (!dead) { dead = true; setDirty(); } }

    @Override public CompoundTag save(CompoundTag tag) {
        tag.putInt("SchemaVersion", SCHEMA); tag.putLong("StartTime", startTime);
        tag.putString("AppearanceOverride", appearanceOverride);
        tag.putLong("MeteorTime", meteorTime); tag.putLong("InfectionTime", infectionTime);
        if (meteor != null) tag.putLong("Meteor", meteor.asLong());
        if (host != null) { tag.putUUID("Host", host); tag.putString("HostName", hostName); }
        if (entityId != null) tag.putUUID("EntityId", entityId);
        tag.putString("EntityDimension", entityDimension); tag.putLong("EntityPosition", entityPosition.asLong());
        tag.putInt("MinimumStage", minimumStage); tag.putBoolean("Transferred", transferred);
        tag.putBoolean("EntityCreated", entityCreated); tag.putBoolean("Dead", dead); tag.put("Quarantine", quarantine.copy());
        ListTag players = new ListTag(); sensitive.stream().sorted().forEach(id -> players.add(NbtUtils.createUUID(id)));
        tag.put("Sensitive", players); return tag;
    }
    public static HorrorState load(CompoundTag tag) {
        if (tag.getInt("SchemaVersion") != SCHEMA) throw new IllegalStateException("Formato del parásito desconocido");
        HorrorState state = new HorrorState(tag.getLong("StartTime"));
        state.appearanceOverride(tag.getString("AppearanceOverride"));
        state.meteorTime = tag.getLong("MeteorTime"); state.infectionTime = tag.getLong("InfectionTime");
        state.meteor = tag.contains("Meteor", Tag.TAG_LONG) ? BlockPos.of(tag.getLong("Meteor")) : null;
        state.host = tag.hasUUID("Host") ? tag.getUUID("Host") : null; state.hostName = tag.getString("HostName");
        state.entityId = tag.hasUUID("EntityId") ? tag.getUUID("EntityId") : null;
        state.entityDimension = tag.getString("EntityDimension"); state.entityPosition = BlockPos.of(tag.getLong("EntityPosition"));
        state.minimumStage = tag.getInt("MinimumStage"); state.transferred = tag.getBoolean("Transferred");
        state.entityCreated = tag.getBoolean("EntityCreated"); state.dead = tag.getBoolean("Dead");
        state.quarantine = tag.getCompound("Quarantine").copy();
        if (state.minimumStage < 0 || state.minimumStage > 4 || state.quarantine.size() > 1024
                || tag.getList("Sensitive", Tag.TAG_INT_ARRAY).size() > 4096
                || (state.host != null && (!state.hostName.matches("[A-Za-z0-9_]{1,16}") || state.entityId == null))
                || (state.transferred && (state.host == null || state.quarantine.isEmpty())))
            throw new IllegalStateException("Estado del parásito inválido");
        for (Tag id : tag.getList("Sensitive", Tag.TAG_INT_ARRAY)) state.sensitive.add(NbtUtils.loadUUID(id));
        state.setDirty(false); return state;
    }
}