package uulm.teamname.marvelous.server.lobbymanager; import org.tinylog.Logger; import uulm.teamname.marvelous.gamelibrary.ArrayTools; import uulm.teamname.marvelous.gamelibrary.Tuple; import uulm.teamname.marvelous.gamelibrary.config.CharacterProperties; import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; import uulm.teamname.marvelous.gamelibrary.messages.server.*; import uulm.teamname.marvelous.gamelibrary.requests.Request; import uulm.teamname.marvelous.server.Server; import uulm.teamname.marvelous.server.lobby.Lobby; import uulm.teamname.marvelous.server.netconnector.ClientState; import uulm.teamname.marvelous.server.netconnector.SUID; import uulm.teamname.marvelous.server.netconnector.UserManager; import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; public class LobbyConnection implements Runnable { private static boolean synchronous = false; public final String gameID; public LobbyConnectionState state = LobbyConnectionState.Waiting; private Participant player1; private Participant player2; private final Set spectators = new HashSet<>(10); private final Map> selection = new HashMap<>(2); public final Map options = new HashMap<>(2); private final BlockingQueue> requestQueue = new LinkedBlockingQueue<>(); private Lobby lobby; /** Creates a new LobbyConnection */ public LobbyConnection(String gameID) { this.gameID = gameID; Tuple picked = Server.getCharacterConfig().getDisjointSetsOfPropertiesOfSize(12); this.options.put(ParticipantType.PlayerOne, picked.item1); this.options.put(ParticipantType.PlayerTwo, picked.item2); } public boolean setSelection(Participant participant, Integer[] selection) { this.selection.put(participant.id, ArrayTools.toArrayList(selection)); return this.selection.size() == 2; } public void addParticipant(Participant participant) { if (this.state == LobbyConnectionState.Started) { Logger.trace("Set client state to playing"); participant.getClient().setState(ClientState.Playing); participant.state = ParticipantState.Playing; participant.sendMessage(generateGameStructure(participant.type)); } if (participant.type == ParticipantType.Spectator) { Logger.trace("Adding spectator"); spectators.add(participant); } else if (participant.type == ParticipantType.PlayerOne) { player1 = participant; } else { player2 = participant; } } /** Disconnects a participant from the LobbyConnection */ public void removeParticipant(Participant participant) { removeParticipant(participant, "You have been disconnected."); } /** Disconnects a participant from the LobbyConnection */ public void removeParticipant(Participant participant, String reason) { if (participant != null) { Logger.debug("Removing participant '{}' with role {} from lobby", participant.id.getName(), participant.type); LobbyManager.getInstance().removeParticipant(participant); UserManager.getInstance().removeClient(participant.getClient(), reason); if (participant.type == ParticipantType.Spectator) { spectators.remove(participant); } else if (participant.type == ParticipantType.PlayerOne) { player1 = null; } else { player2 = null; } } } /** Returns the next free slot in the lobby as a {@link ParticipantType} */ public ParticipantType freeSlot() { if (player1 == null) { return ParticipantType.PlayerOne; } else if (player2 == null) { return ParticipantType.PlayerTwo; } else { return ParticipantType.Spectator; } } /** Returns whether there is a player slot available in the lobby */ public boolean hasFreePlayerSlot() { return player1 == null || player2 == null; } public Participant getPlayer1() { return player1; } public Participant getPlayer2() { return player2; } public Set getSpectators() { return spectators; } public boolean hasPlayer1() { return player1 != null; } public boolean hasPlayer2() { return player2 != null; } public void handleMessage(Participant participant, Request[] requests) { if(synchronous) { lobby.receiveRequests(requests, participant); return; } try { this.requestQueue.put(Tuple.of(participant, requests)); } catch (InterruptedException ignored) { } } /** Handles disconnect of a Participant. Hereby, the participant is made ready for reconnection */ public void handleDisconnect(Participant participant) { participant.disconnected = true; if (state == LobbyConnectionState.Started) { lobby.handleDisconnect(participant); } } /** Handles reconnect of a Participant. Hereby, the participant is made ready for reconnection */ public void handleReconnect(Participant participant) { participant.disconnected = false; if (state == LobbyConnectionState.Started) { GeneralAssignmentMessage response = new GeneralAssignmentMessage(); response.gameID = gameID; participant.sendMessage(response); participant.sendMessage(generateGameStructure(participant.type)); lobby.handleReconnect(participant); } } public void runSynchronous() { synchronous = true; run(); } @Override public void run() { state = LobbyConnectionState.Started; player1.state = ParticipantState.Playing; player2.state = ParticipantState.Playing; for (Participant spectator : spectators) { spectator.state = ParticipantState.Playing; } if(!synchronous) { Logger.info("Starting Lobby thread for lobby '{}'", gameID); }else { Logger.info("Starting Lobby in main thread for lobby '{}'", gameID); } broadcastGameStructure(); this.lobby = new Lobby( gameID, this, Server.getPartyConfig(), Server.getCharacterConfig(), Server.getScenarioConfig(), selection.get(player1.id), selection.get(player2.id) ); if(synchronous) { return; } while (state == LobbyConnectionState.Started) { Tuple currentRequests = pollQueueAsync(); if (currentRequests != null) { lobby.receiveRequests(currentRequests.item2, currentRequests.item1); } } } public void terminate() { LobbyRunner.getInstance().removeLobby(this); LobbyManager.getInstance().removeLobby(gameID); state = LobbyConnectionState.Aborted; removeParticipant(player1); removeParticipant(player2); spectators.forEach(this::removeParticipant); } private void broadcastGameStructure() { // Sending GameStructure message with fitting assignment player1.sendMessage(generateGameStructure(ParticipantType.PlayerOne)); player2.sendMessage(generateGameStructure(ParticipantType.PlayerTwo)); broadcastToSpectators(generateGameStructure(ParticipantType.Spectator)); } private GameStructureMessage generateGameStructure(ParticipantType assignment) { GameStructureMessage gameStructureMessage = new GameStructureMessage(); gameStructureMessage.playerOneName = player1.id.getName(); gameStructureMessage.playerTwoName = player2.id.getName(); gameStructureMessage.playerOneCharacters = new CharacterProperties[6]; gameStructureMessage.playerTwoCharacters = new CharacterProperties[6]; int i = 0; for (Integer id : selection.get(player1.id)) { gameStructureMessage.playerOneCharacters[i++] = Server.getCharacterConfig().getIDMap().get(id); } i = 0; for (Integer id : selection.get(player2.id)) { gameStructureMessage.playerTwoCharacters[i++] = Server.getCharacterConfig().getIDMap().get(id); } gameStructureMessage.matchconfig = Server.getPartyConfig(); gameStructureMessage.scenarioconfig = Server.getScenarioConfig(); gameStructureMessage.assignment = assignment; return gameStructureMessage; } private Tuple pollQueueAsync() { Tuple current = null; try { current = requestQueue.poll(1000, TimeUnit.MILLISECONDS); } catch (InterruptedException ignored) { } return current; } private void broadcast(BasicMessage message) { Logger.trace("Broadcasting message of type {} to all members of lobby", message.messageType); if (player1 != null) player1.sendMessage(message); if (player2 != null) player2.sendMessage(message); spectators.forEach(spectator -> spectator.sendMessage(message)); } private void broadcastToSpectators(BasicMessage message) { Logger.trace("Broadcasting message of type {} to all spectators", message.messageType); spectators.forEach(spectator -> spectator.sendMessage(message)); } private void broadcastToAllExcept(Participant except, BasicMessage message) { Logger.trace("Sending message of type {} to all except participant with role {}", message.messageType, except == null ? "NONE" : except.type); if (except != null) { if (!except.equals(player1)) player1.sendMessage(message); if (!except.equals(player2)) player2.sendMessage(message); for (Participant spectator : spectators) { if (!except.equals(spectator)) { spectator.sendMessage(message); } } } else { broadcast(message); } } public void sendEvents(Participant recipient, Event... events) { Logger.trace("Sending {} events to participant with role {}", events.length, recipient == null ? "NONE" : recipient.type); if (recipient != null) { EventMessage message = new EventMessage(); message.messages = events; recipient.sendMessage(message); } } public void broadcastEvents(List events) { broadcastEvents(events.toArray(new Event[0])); } public void broadcastEvents(Event... events) { Logger.trace("Broadcasting {} events", events.length); EventMessage message = new EventMessage(); message.messages = events; broadcast(message); } public void broadcastToAllExcept(Participant except, Event... events) { Logger.trace("Broadcasting {} events to all except participant with role {}", events.length, except == null ? "NONE" : except.type); var message = new EventMessage(); message.messages = events; broadcastToAllExcept(except, message); } }