279 lines
11 KiB
Java
279 lines
11 KiB
Java
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;
|
|
import uulm.teamname.marvelous.gamelibrary.messages.client.*;
|
|
import uulm.teamname.marvelous.gamelibrary.messages.server.GoodbyeClientMessage;
|
|
import uulm.teamname.marvelous.gamelibrary.messages.server.HelloClientMessage;
|
|
import uulm.teamname.marvelous.server.Server;
|
|
import uulm.teamname.marvelous.server.lobbymanager.LobbyManager;
|
|
|
|
import org.java_websocket.WebSocket;
|
|
|
|
import java.util.*;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
/**
|
|
* Class that manages users. It is meant as an extension to the {@link MarvelousServer} class. This is the first place
|
|
* where messages are relayed to after they get received. This class is responsible for handshakes, reconnects and
|
|
* WebSocket-to-Participant matching. It is designed to be thread-safe.
|
|
*/
|
|
public class UserManager {
|
|
private static UserManager instance;
|
|
private static final String errorInvalidMessage = "Invalid message.";
|
|
|
|
/**
|
|
* @return the current instance of the UserManager
|
|
*/
|
|
public static UserManager getInstance() {
|
|
if (instance == null) {
|
|
instance = new UserManager();
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
/** A map of all connected clients. */
|
|
private final HashMap<WebSocket, Client> clients = new HashMap<>();
|
|
|
|
public final JSON json;
|
|
|
|
/** Constructs a new, empty UserManager */
|
|
private UserManager() {
|
|
this.json = new JSON(Server.getCharacterConfig());
|
|
}
|
|
|
|
/** Called on a new WebSocket connection. */
|
|
public void connectUser(WebSocket conn) {
|
|
Logger.debug("Connected new user");
|
|
synchronized(clients) {
|
|
clients.put(conn, new Client(conn));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called on any received messages. The method checks the message for validity, and then relays it accordingly.
|
|
* @param conn is the {@link WebSocket} that sent the message
|
|
* @param message is the {@link String} sent by the connection
|
|
*/
|
|
public 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<BasicMessage> parsed = json.parse(message);
|
|
|
|
if(parsed.isEmpty()) {
|
|
Logger.debug("Message couldn't be parsed, sending error...");
|
|
client.sendError("Message could not be parsed.");
|
|
return;
|
|
}
|
|
|
|
BasicMessage data = parsed.get();
|
|
Optional<String> 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. */
|
|
public 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;
|
|
handleHelloServerMessage(client, message);
|
|
|
|
} else if(data instanceof ReconnectMessage) {
|
|
ReconnectMessage message = (ReconnectMessage) data;
|
|
handleReconnectMessage(client, message);
|
|
|
|
} else if(data instanceof PlayerReadyMessage) {
|
|
Logger.trace("Message was instanceof PlayerReadyMessage");
|
|
PlayerReadyMessage message = (PlayerReadyMessage) data;
|
|
handlePlayerReadyMessage(client, message);
|
|
|
|
} else if(data instanceof CharacterSelectionMessage) {
|
|
Logger.trace("Message was instanceof CharacterSelectionMessage");
|
|
CharacterSelectionMessage message = (CharacterSelectionMessage) data;
|
|
handleCharacterSelectionMessage(client, message);
|
|
|
|
} else if(data instanceof RequestMessage) {
|
|
Logger.trace("Message was instanceof RequestMessage");
|
|
RequestMessage message = (RequestMessage) data;
|
|
handleRequestsMessage(client, message);
|
|
|
|
}
|
|
}
|
|
|
|
/** Handles a HelloServerMessage */
|
|
private void handleHelloServerMessage(Client client, HelloServerMessage message) {
|
|
if(client.getState() != ClientState.Blank) {
|
|
Logger.debug("Disconnecting client as ClientState isn't Blank but {}", client.getState());
|
|
client.sendError("Invalid message, as handshake already completed.");
|
|
return;
|
|
}
|
|
|
|
client.setId(new SUID(message.name, message.deviceID));
|
|
|
|
Logger.trace("forwarding message to the LobbyManager");
|
|
AtomicBoolean running = new AtomicBoolean(false);
|
|
if(LobbyManager.getInstance().handleConnect(client, running)) {
|
|
|
|
var clientHasRunningGame = running.get();
|
|
if (clientHasRunningGame) {
|
|
client.setState(ClientState.Reconnect);
|
|
} else {
|
|
client.setState(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.getState() != ClientState.Reconnect) {
|
|
client.sendError("Invalid message, as client is not in reconnect-ready state");
|
|
return;
|
|
}
|
|
|
|
if(Boolean.TRUE.equals(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.setState(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, setting client to ready to connect state");
|
|
client.setState(ClientState.Ready);
|
|
}
|
|
}
|
|
|
|
/** 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.getState() != ClientState.Ready) {
|
|
Logger.debug("Client wasn't in Ready state but instead in {} state, sending error...", client.getState());
|
|
client.sendError("Invalid message, as client is not in Ready state");
|
|
return;
|
|
}
|
|
|
|
Logger.trace("Relaying message to LobbyManager");
|
|
if(Boolean.TRUE.equals(message.startGame)) {
|
|
if(LobbyManager.getInstance().handleReady(client, message)) {
|
|
client.setState(ClientState.Assigned);
|
|
} else {
|
|
Logger.trace("Sending error to client as message couldn't be processed properly");
|
|
client.sendError(errorInvalidMessage);
|
|
}
|
|
} 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.getState() != ClientState.Assigned) {
|
|
Logger.debug("Couldn't handle CharacterSelectionMessage as client wasn't in assignedState but in {}",
|
|
client.getState());
|
|
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");
|
|
} else {
|
|
client.sendError(errorInvalidMessage);
|
|
}
|
|
}
|
|
|
|
/** Handles a RequestMessage, and forwards it to the LobbyManager if valid */
|
|
private void handleRequestsMessage(Client client, RequestMessage message) {
|
|
Logger.trace("Handling RequestMessage");
|
|
if(client.getState() != ClientState.Playing) {
|
|
Logger.debug("Couldn't handle RequestMessage as client wasn't in playingState but in {}",
|
|
client.getState());
|
|
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(errorInvalidMessage);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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;
|
|
|
|
client.sendMessage(response);
|
|
|
|
client.socket.close(CloseFrame.NORMAL);
|
|
}
|
|
|
|
|
|
public int getUserCount() {
|
|
return clients.size();
|
|
}
|
|
|
|
public boolean containsConnection(WebSocket conn) {
|
|
return clients.containsKey(conn);
|
|
}
|
|
}
|