diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java index a4d91ff..360f4f2 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java @@ -34,15 +34,19 @@ public class LobbyManager { private final HashMap participants = new HashMap<>(); + /** + * Handles a newly connected Client, and returns whether the game is already running in the given {@link + * AtomicBoolean} + */ public boolean handleConnect(Client client, AtomicBoolean running) { - if(participants.containsKey(client.id)) { + if (participants.containsKey(client.id)) { running.set(true); } return true; } public boolean handleReady(Client client, PlayerReadyMessage message) { - if(participants.containsKey(client.id)) { + if (participants.containsKey(client.id)) { return false; } @@ -51,7 +55,7 @@ public class LobbyManager { } public boolean handleReconnect(Client client) { - if(!participants.containsKey(client.id)) { + if (!participants.containsKey(client.id)) { return false; } @@ -60,7 +64,7 @@ public class LobbyManager { LobbyConnection lobby = lobbies.get(participant.lobby); - if(lobby == null) { + if (lobby == null) { return false; } @@ -69,20 +73,28 @@ public class LobbyManager { return true; } + /** + * Handles a {@link CharacterSelectionMessage}, computes the characters that have been selected and relays that + * information to the {@link LobbyConnection} concerned by this information. + * + * @param client is the client that sent the message + * @param message is the message sent by the client + * @return true if handled successfully, and false otherwise + */ public boolean handleSelection(Client client, CharacterSelectionMessage message) { - if(!participants.containsKey(client.id)) { + if (!participants.containsKey(client.id)) { return false; } Participant participant = participants.get(client.id); - if(participant.state != ParticipantState.Assigned) { + if (participant.state != ParticipantState.Assigned) { return false; } LobbyConnection lobby = lobbies.get(participant.lobby); - if(lobby == null) { + if (lobby == null) { return false; } @@ -91,13 +103,13 @@ public class LobbyManager { CharacterProperties[] options = lobby.options.get(participant.type); int n = 0; - for(int i = 0; i < 12; i++) { - if(message.characters[i]) { + for (int i = 0; i < 12; i++) { + if (message.characters[i]) { selected[n++] = options[i].characterID; } } - if(n != 6) { + if (n != 6) { return false; } @@ -109,27 +121,34 @@ public class LobbyManager { response.selectionComplete = complete; participant.sendMessage(response); - if(complete) { + if (complete) { LobbyRunner.getInstance().startLobby(lobby); } return true; } + /** + * Handles a requestMessage, and relays it to the Lobby if valid + * + * @param client is the client that sent the message + * @param message is the message sent by the client + * @return true if handled successfully, and false otherwise + */ public boolean handleRequests(Client client, RequestMessage message) { - if(!participants.containsKey(client.id)) { + if (!participants.containsKey(client.id)) { return false; } Participant participant = participants.get(client.id); - if(participant.state != ParticipantState.Playing) { + if (participant.state != ParticipantState.Playing) { return false; } LobbyConnection lobby = lobbies.get(participant.lobby); - if(lobby == null) { + if (lobby == null) { return false; } @@ -139,11 +158,11 @@ public class LobbyManager { } public void handleDisconnect(Client client, boolean byRemote) { - if(!participants.containsKey(client.id)) { + if (!participants.containsKey(client.id)) { return; } - if(!byRemote) { + if (!byRemote) { participants.remove(client.id); } @@ -151,7 +170,7 @@ public class LobbyManager { LobbyConnection lobby = lobbies.get(participant.lobby); - if(lobby == null) { + if (lobby == null) { return; } @@ -164,8 +183,8 @@ public class LobbyManager { } private void addParticipant(Client client, String lobbyID, RoleEnum role) { - if(!lobbies.containsKey(lobbyID)) { - if(!LobbyRunner.getInstance().canAddLobby()) { + if (!lobbies.containsKey(lobbyID)) { + if (!LobbyRunner.getInstance().canAddLobby()) { UserManager.getInstance().removeClient(client, "The server is currently full."); return; } @@ -176,7 +195,7 @@ public class LobbyManager { ParticipantType type = lobby.freeSlot(); - if(type == ParticipantType.Spectator && role != RoleEnum.SPECTATOR) { + if (type == ParticipantType.Spectator && role != RoleEnum.SPECTATOR) { UserManager.getInstance().removeClient(client, "The game is already full. Please connect as a spectator instead."); return; } @@ -186,12 +205,12 @@ public class LobbyManager { lobby.addParticipant(participant); - if(type != ParticipantType.Spectator) { + if (type != ParticipantType.Spectator) { GameAssignmentMessage response = new GameAssignmentMessage(); response.gameID = lobby.gameID; response.characterSelection = lobby.options.get(type); participant.sendMessage(response); - }else { + } else { GeneralAssignmentMessage response = new GeneralAssignmentMessage(); response.gameID = lobby.gameID; participant.sendMessage(response); diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java index 08b7148..234171f 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java @@ -1,6 +1,7 @@ package uulm.teamname.marvelous.server.netconnector; import org.java_websocket.framing.CloseFrame; +import org.tinylog.Logger; import uulm.teamname.marvelous.gamelibrary.json.JSON; import uulm.teamname.marvelous.gamelibrary.json.ValidationUtility; import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; @@ -45,6 +46,7 @@ public class UserManager { /** Called on a new WebSocket connection. Places the WebSocket and its ResourceDescriptor in a HashMap. */ void connectUser(WebSocket conn) { + Logger.debug("Connected new user"); synchronized(clients) { clients.put(conn, new Client(conn)); } @@ -56,15 +58,19 @@ public class UserManager { * @param message is the {@link String} sent by the connection */ void messageReceived(WebSocket conn, String message) { + Logger.debug("Message received from {}", conn); Client client = clients.get(conn); if(client == null) { + Logger.warn("messageReceived called with null-valued client. This is probably a bug."); return; } + Logger.trace("Parsing message..."); Optional parsed = json.parse(message); if(parsed.isEmpty()) { + Logger.debug("Message couldn't be parsed, sending error..."); client.sendError("Message could not be parsed."); return; } @@ -73,108 +79,179 @@ public class UserManager { Optional violations = ValidationUtility.validate(data); if(violations.isPresent()) { + Logger.debug("The message that client sent had structural violations: '{}'. Sending error...", + violations.get()); client.sendError(violations.get()); return; } + Logger.trace("Handling message..."); handleMessage(client, data); } /** Called on closed connection. */ void disconnectUser(WebSocket conn, boolean closedByRemote) { + Logger.info("Disconnecting client '{}'", conn); Client client = clients.get(conn); if(client == null) { + Logger.warn("disconnect called with null-valued client. This is probably a bug."); return; } + Logger.trace("Calling handleDisconnect on LobbyManager"); LobbyManager.getInstance().handleDisconnect(client, closedByRemote); synchronized(clients) { + Logger.debug("Removing connections from clients HashMap"); clients.remove(conn); } } + /** Checks type of a given message, and delegates its execution to the respective handler method */ private void handleMessage(Client client, BasicMessage data) { + Logger.debug("Received message from client '{}'", client); if(data instanceof HelloServerMessage) { + Logger.trace("Message was instanceof HelloServerMessage"); HelloServerMessage message = (HelloServerMessage) data; - if(client.state != ClientState.Blank) { - client.sendError("Invalid message."); - return; - } + handleHelloServerMessage(client, message); - client.id = new SUID(message.name, message.deviceID); - - AtomicBoolean running = new AtomicBoolean(false); - if(LobbyManager.getInstance().handleConnect(client, running)) { - client.state = ClientState.Ready; - - HelloClientMessage response = new HelloClientMessage(); - response.runningGame = running.get(); - client.sendMessage(response); - } else { - client.sendError("Invalid message."); - } } else if(data instanceof ReconnectMessage) { ReconnectMessage message = (ReconnectMessage) data; - if(client.state != ClientState.Ready) { - client.sendError("Invalid message."); - return; - } + handleReconnectMessage(client, message); - if(message.reconnect) { - if(LobbyManager.getInstance().handleReconnect(client)) { - client.state = ClientState.Playing; - } else { - client.sendError("Invalid message."); - } - } else { - client.state = ClientState.Blank; - } } else if(data instanceof PlayerReadyMessage) { + Logger.trace("Message was instanceof PlayerReadyMessage"); PlayerReadyMessage message = (PlayerReadyMessage) data; - if(client.state != ClientState.Ready) { - client.sendError("Invalid message."); - return; - } + handlePlayerReadyMessage(client, message); - if(message.startGame) { - if(LobbyManager.getInstance().handleReady(client, message)) { - client.state = ClientState.Assigned; - } else { - client.sendError("Invalid message."); - } - } else { - removeClient(client, "You got disconnected."); - } } else if(data instanceof CharacterSelectionMessage) { + Logger.trace("Message was instanceof CharacterSelectionMessage"); CharacterSelectionMessage message = (CharacterSelectionMessage) data; - if(client.state != ClientState.Assigned) { - client.sendError("Invalid message."); - return; - } + handleCharacterSelectionMessage(client, message); - if(LobbyManager.getInstance().handleSelection(client, message)) { - client.state = ClientState.Playing; - } else { - client.sendError("Invalid message."); - } } else if(data instanceof RequestMessage) { + Logger.trace("Message was instanceof RequestMessage"); RequestMessage message = (RequestMessage) data; - if(client.state != ClientState.Playing) { - client.sendError("Invalid message."); - return; - } + handleRequestsMessage(client, message); - if(LobbyManager.getInstance().handleRequests(client, message)) { - //"👍 i approve" - the server - } else { - client.sendError("Invalid message."); - } } } + /** Handles a HelloServerMessage */ + private void handleHelloServerMessage(Client client, HelloServerMessage message) { + if(client.state != ClientState.Blank) { + Logger.debug("Disconnecting client as ClientState isn't Blank but {}", client.state); + client.sendError("Invalid message, as handshake already completed."); + return; + } + + client.id = new SUID(message.name, message.deviceID); + + Logger.trace("forwarding message to the LobbyManager"); + AtomicBoolean running = new AtomicBoolean(false); + if(LobbyManager.getInstance().handleConnect(client, running)) { + client.state = ClientState.Ready; + + HelloClientMessage response = new HelloClientMessage(); + response.runningGame = running.get(); + client.sendMessage(response); + } else { + client.sendError("Message could not be processed."); + } + } + + /** Handles a reconnectMessage, and reconnects the client if needed */ + private void handleReconnectMessage(Client client, ReconnectMessage message) { + if(client.state != ClientState.Ready) { + client.sendError("Invalid message."); + return; + } + + if(message.reconnect) { + Logger.trace("Reconnecting to lobby. Forwarding reconnect instruction to the LobbyManager"); + if(LobbyManager.getInstance().handleReconnect(client)) { + Logger.trace("Successfully reconnected client, changing state to Playing..."); + client.state = ClientState.Playing; + } else { + Logger.debug("The client couldn't be reconnected properly"); + client.sendError("You could not be reconnected to the Lobby"); + } + } else { + Logger.trace("No reconnect requested, changing ClientState to blank"); + client.state = ClientState.Blank; + } + } + + /** Handles a PlayerReadyMessage, and connects a client to a lobby if valid */ + private void handlePlayerReadyMessage(Client client, PlayerReadyMessage message) { + Logger.trace("Handing PlayerReadyMessage..."); + if(client.state != ClientState.Ready) { + Logger.debug("Client wasn't in Ready state but instead in {} state, sending error...", client.state); + client.sendError("Invalid message, as client is not in Ready state"); + return; + } + + Logger.trace("Relaying message to LobbyManager"); + if(message.startGame) { + if(LobbyManager.getInstance().handleReady(client, message)) { + client.state = ClientState.Assigned; + } else { + Logger.trace("Sending error to client as message couldn't be processed properly"); + client.sendError("Invalid message."); + } + } else { + Logger.debug("Disconnecting client as game couldn't be started"); + removeClient(client, "You got disconnected."); + } + } + + /** Handles a characterSelectionMessage, and forwards it to the LobbyManager if valid */ + private void handleCharacterSelectionMessage(Client client, CharacterSelectionMessage message) { + Logger.trace("Handling CharacterSelectionMessage"); + if(client.state != ClientState.Assigned) { + Logger.debug("Couldn't handle CharacterSelectionMessage as client wasn't in assignedState but in {}", + client.state); + client.sendError("Cannot select character, as client is not in the Character Selection phase"); + return; + } + + Logger.trace("relaying message to be handled by the LobbyManager..."); + if(LobbyManager.getInstance().handleSelection(client, message)) { + Logger.trace("Handled successfully"); + client.state = ClientState.Playing; + } else { + client.sendError("Invalid message."); + } + } + + /** Handles a RequestMessage, and forwards it to the LobbyManager if valid */ + private void handleRequestsMessage(Client client, RequestMessage message) { + Logger.trace("Handling RequestMessage"); + if(client.state != ClientState.Playing) { + Logger.debug("Couldn't handle RequestMessage as client wasn't in assignedState but in {}", + client.state); + client.sendError("Invalid message, as client is not ingame"); + return; + } + + Logger.trace("relaying message to be handled by the LobbyManager..."); + if(LobbyManager.getInstance().handleRequests(client, message)) { + Logger.trace("Handled successfully"); + //"i approve" - the server + } else { + Logger.debug("Message couldn't be handled, sending error to client"); + client.sendError("Invalid message."); + } + } + + + /** + * Called when a client should be removed from the game. + * @param client is the client to be removed + * @param message is the message that is sent to the client in the accompanying {@link GoodbyeClientMessage} + */ public void removeClient(Client client, String message) { GoodbyeClientMessage response = new GoodbyeClientMessage(); response.message = message;