Server/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java

259 lines
9.1 KiB
Java

package uulm.teamname.marvelous.server.lobbymanager;
import org.tinylog.Logger;
import uulm.teamname.marvelous.gamelibrary.config.CharacterProperties;
import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType;
import uulm.teamname.marvelous.gamelibrary.messages.RoleEnum;
import uulm.teamname.marvelous.gamelibrary.messages.client.CharacterSelectionMessage;
import uulm.teamname.marvelous.gamelibrary.messages.client.PlayerReadyMessage;
import uulm.teamname.marvelous.gamelibrary.messages.client.RequestMessage;
import uulm.teamname.marvelous.gamelibrary.messages.server.ConfirmSelectionMessage;
import uulm.teamname.marvelous.gamelibrary.messages.server.GameAssignmentMessage;
import uulm.teamname.marvelous.gamelibrary.messages.server.GeneralAssignmentMessage;
import uulm.teamname.marvelous.server.netconnector.Client;
import uulm.teamname.marvelous.server.netconnector.ClientState;
import uulm.teamname.marvelous.server.netconnector.SUID;
import uulm.teamname.marvelous.server.netconnector.UserManager;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class LobbyManager {
private static LobbyManager instance;
/**
* @return the current instance of the LobbyManager
*/
public static LobbyManager getInstance() {
if (instance == null) {
instance = new LobbyManager();
}
return instance;
}
private final HashMap<String, LobbyConnection> lobbies = new HashMap<>();
private final HashMap<SUID, Participant> 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.getId())) {
running.set(true);
}
return true;
}
public boolean handleReady(Client client, PlayerReadyMessage message) {
if (participants.containsKey(client.getId())) {
return false;
}
addParticipant(client, client.socket.getResourceDescriptor(), message.role);
return true;
}
public boolean handleReconnect(Client client) {
if (!participants.containsKey(client.getId())) {
return false;
}
Participant participant = participants.get(client.getId());
participant.setClient(client);
LobbyConnection lobby = lobbies.get(participant.lobby);
if (lobby == null) {
return false;
}
lobby.handleReconnect(participant);
return true;
}
/**
* Adds a participant to a lobby. If the maximum amount of lobbies is already filled, or if the lobby requested
* isn't free, the participant is disconnected.
*/
private void addParticipant(Client client, String lobbyID, RoleEnum role) {
Logger.trace("Adding participant '{}' to the lobby '{}'", client.getId().getName(), lobbyID);
if (!lobbies.containsKey(lobbyID)) {
if (!LobbyRunner.getInstance().canAddLobby()) {
Logger.info("Rejecting participant '{}' as server is already full", client.getId().getName());
UserManager.getInstance().removeClient(client, "The server has currently its maximum" +
"lobby number. Please connect as a spectator instead.");
return;
}
Logger.info("Lobby '{}' didn't exist yet, initializing", lobbyID);
lobbies.put(lobbyID, new LobbyConnection(lobbyID));
}
LobbyConnection lobby = lobbies.get(lobbyID);
if (!lobby.hasFreePlayerSlot() && role != RoleEnum.SPECTATOR) {
Logger.debug("No free player slots available, disconnecting client '{}'", client.getId().getName());
UserManager.getInstance()
.removeClient(client, "The lobby your requested is already full. " +
"Please connect as a spectator instead.");
return;
}
ParticipantType type;
if (role == RoleEnum.SPECTATOR) {
type = ParticipantType.Spectator;
} else {
type = lobby.freeSlot();
}
Logger.trace("New participant '{}' has the role '{}'", client.getId().getName(), type);
Participant participant = new Participant(client, lobbyID, type);
participants.put(client.getId(), participant);
lobby.addParticipant(participant);
if (type != ParticipantType.Spectator) {
Logger.debug("Sending GameAssignment message to user '{}'", client.getId().getName());
GameAssignmentMessage response = new GameAssignmentMessage();
response.gameID = lobby.gameID;
response.characterSelection = lobby.options.get(type);
participant.sendMessage(response);
} else {
Logger.debug("Sending GeneralAssignment message to user '{}'", client.getId().getName());
GeneralAssignmentMessage response = new GeneralAssignmentMessage();
response.gameID = lobby.gameID;
participant.sendMessage(response);
}
}
/**
* 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) {
Logger.debug("Handling characterSelection...");
if (!participants.containsKey(client.getId())) {
Logger.trace("Participant didn't exist, returning...");
return false;
}
Participant participant = participants.get(client.getId());
if (participant.state != ParticipantState.Assigned) {
Logger.trace("Participant wasn't assigned, exiting...");
return false;
} else if (participant.type == ParticipantType.Spectator) {
Logger.trace("Spectator sent message, returning...");
return false;
}
LobbyConnection lobby = lobbies.get(participant.lobby);
if (lobby == null) {
return false;
}
Integer[] selected = new Integer[6];
CharacterProperties[] options = lobby.options.get(participant.type);
int n = 0;
for (int i = 0; i < 12; i++) {
if (message.characters[i]) {
selected[n++] = options[i].characterID;
}
}
if (n != 6) {
return false;
}
participant.state = ParticipantState.Selected;
boolean complete = lobby.setSelection(participant, selected);
if (complete) {
lobby.getPlayer1().getClient().setState(ClientState.Playing);
lobby.getPlayer2().getClient().setState(ClientState.Playing);
lobby.getSpectators().forEach(spectator -> spectator.getClient().setState(ClientState.Playing));
LobbyRunner.getInstance().startLobby(lobby);
} else {
ConfirmSelectionMessage response = new ConfirmSelectionMessage();
response.selectionComplete = false;
participant.sendMessage(response);
}
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.getId())) {
return false;
}
Participant participant = participants.get(client.getId());
if (participant.state != ParticipantState.Playing) {
return false;
}
LobbyConnection lobby = lobbies.get(participant.lobby);
if (lobby == null) {
return false;
}
lobby.handleMessage(participant, message.messages);
return true;
}
/**
* Handles the disconnect of a WebSocket. Note that this is not a leave from the game, but instead just that:
* a reconnectable disconnect.
*/
public void handleDisconnect(Client client, boolean byRemote) {
Logger.trace("Handling disconnect of Client");
if (!participants.containsKey(client.getId())) {
return;
}
Participant participant = participants.get(client.getId());
LobbyConnection lobby = lobbies.get(participant.lobby);
if (lobby == null) {
return;
}
lobby.handleDisconnect(participant);
}
/**
* Removes a participant from the game entirely. This is done when for example a player gets removed from the
* Lobby because of a timeout.
*/
public void removeParticipant(Participant participant) {
if (participant != null) {
participants.remove(participant.id);
}
}
}