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;
}
}