Volver al índice
src/main/java/ar/com/companeros/decision/StrategicGate.java
package ar.com.companeros.decision;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.LongSupplier;
/** Permisos de Sol emitidos por estado confiable de facción, nunca por la frase del modelo. */
public final class StrategicGate {
public enum Reason { MAIN_BASE, ABANDON_OR_RECOVER_BASE, FACTION_DEFENSE, DEFEAT_RECOVERY,
COUNTERATTACK, FACTION_CONFLICT, UNRESOLVED_STRATEGY }
public record Permit(UUID id, UUID factionId, UUID leaderId, Reason reason, long expiresAtUtc) { }
private static final long COOLDOWN = 6 * 60 * 60 * 1000L;
private static final long DAY = 24 * 60 * 60 * 1000L;
private final boolean enabled;
private final LongSupplier clock;
private final Map<UUID, UUID> leaders = new HashMap<>();
private final Map<UUID, Long> lastCalls = new HashMap<>();
private final Map<UUID, Permit> issued = new HashMap<>();
private final ArrayDeque<Long> calls = new ArrayDeque<>();
public StrategicGate(boolean enabled) { this(enabled, System::currentTimeMillis); }
public StrategicGate(boolean enabled, LongSupplier clock) { this.enabled = enabled; this.clock = clock; }
// Estas operaciones pertenecen al servicio de facciones del servidor.
public synchronized void recognizeLeader(UUID faction, UUID leader) {
if (leaders.size() >= 256 && !leaders.containsKey(faction))
throw new IllegalStateException("Registro estratégico fuera de límite");
leaders.put(java.util.Objects.requireNonNull(faction), java.util.Objects.requireNonNull(leader));
issued.values().removeIf(permit -> permit.factionId().equals(faction));
}
public synchronized void revokeLeader(UUID faction) {
leaders.remove(faction); issued.values().removeIf(permit -> permit.factionId().equals(faction));
}
public synchronized Optional<Permit> authorize(UUID faction, UUID actor, Reason reason, boolean collectiveImpact) {
long now = clock.getAsLong(); prune(now);
if (!enabled || !collectiveImpact || reason == null || !actor.equals(leaders.get(faction))
|| calls.size() >= 4 || issued.size() >= 16 || coolingDown(faction, now)) return Optional.empty();
Permit permit = new Permit(UUID.randomUUID(), faction, actor, reason, now + 60_000);
issued.put(permit.id(), permit); return Optional.of(permit);
}
synchronized boolean consume(Permit permit, UUID actor) {
long now = clock.getAsLong(); prune(now);
if (!enabled || permit == null || !permit.equals(issued.get(permit.id()))
|| !actor.equals(permit.leaderId()) || !actor.equals(leaders.get(permit.factionId()))
|| calls.size() >= 4 || coolingDown(permit.factionId(), now)) return false;
issued.remove(permit.id()); lastCalls.put(permit.factionId(), now); calls.addLast(now); return true;
}
private boolean coolingDown(UUID faction, long now) {
Long last = lastCalls.get(faction); return last != null && now - last < COOLDOWN;
}
private void prune(long now) {
issued.values().removeIf(permit -> permit.expiresAtUtc() <= now);
while (!calls.isEmpty() && now - calls.peekFirst() >= DAY) calls.removeFirst();
lastCalls.entrySet().removeIf(entry -> now - entry.getValue() >= DAY);
}
}