package tc.oc.commons.bungee.servers; import java.util.Comparator; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import java.util.stream.Stream; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import net.md_5.bungee.api.config.ServerInfo; import tc.oc.api.docs.Server; import tc.oc.api.docs.virtual.ServerDoc; import tc.oc.api.model.ModelListener; import tc.oc.commons.core.logging.Loggers; import tc.oc.commons.core.plugin.PluginFacet; import tc.oc.commons.core.stream.BiStream; import static java.util.stream.Collectors.toSet; /** * Track available lobbies and choose the best lobby to receive players at any given time */ @Singleton public class LobbyTracker implements ModelListener, PluginFacet { private final Logger logger; private final Server localServer; private final ServerTracker serverTracker; protected final Map<Server, ServerInfo> activeLobbies = new ConcurrentHashMap<>(); @Inject LobbyTracker(Loggers loggers, Server localServer, ServerTracker serverTracker) { this.logger = loggers.get(getClass()); this.localServer = localServer; this.serverTracker = serverTracker; } /** * Return the set of protocol versions supported by at least one active lobby */ public Stream<Integer> supportedProtocols() { return activeLobbies.keySet() .stream() .flatMap(server -> server.protocol_versions().stream()); } /** * Choose the best lobby to join with the given protocol */ public Optional<ServerInfo> chooseLobby(int protocol) { return chooseLobby(protocol, null); } /** * Choose the best lobby to join with the given protocol, * besides the given excluded lobby. */ public Optional<ServerInfo> chooseLobby(int protocol, @Nullable ServerInfo excluded) { if(activeLobbies.isEmpty()) { logger.severe("No active lobbies"); return Optional.empty(); } return BiStream.from(activeLobbies) .filterKeys(server -> server.protocol_versions().contains(protocol)) .filterValues(info -> !Objects.equals(excluded, info)) .maxByKey(Comparator.comparing(Server::num_online)) .map(Map.Entry::getValue); } @HandleModel public void serverUpdated(@Nullable Server before, @Nullable Server after, Server latest) { register(latest); } private void register(Server server) { final Optional<ServerInfo> info = Optional.of(server) .filter(this::isActiveLobby) .flatMap(serverTracker::serverInfo); if(info.isPresent()) { if(activeLobbies.put(server, info.get()) == null) { logger.fine("Added lobby " + server.bungee_name()); } } else { if(activeLobbies.remove(server) != null) { logger.fine("Removed lobby " + server.bungee_name()); } } } private boolean isActiveLobby(Server server) { return server.role() == ServerDoc.Role.LOBBY && server.restart_queued_at() == null && localServer.datacenter().equals(server.datacenter()); } }